From e18c52d3937d4dccc2e9284b32bb86b1e7dac45b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 25 Mar 2022 12:10:50 -0400
Subject: fixed annotations on web pages to be scaled to the correct location.

---
 src/client/views/pdf/PDFViewer.tsx | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

(limited to 'src/client/views/pdf/PDFViewer.tsx')

diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 1856c5353..651b0401b 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -580,14 +580,17 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 {this.overlayInfo}
                 {this.standinViews}
                 {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
-                    <MarqueeAnnotator rootDoc={this.props.rootDoc} scrollTop={0} down={this._marqueeing}
+                    <MarqueeAnnotator rootDoc={this.props.rootDoc}
+                        getPageFromScroll={this.getPageFromScroll}
                         anchorMenuClick={this.props.anchorMenuClick}
+                        scrollTop={0}
+                        down={this._marqueeing}
                         addDocument={(doc: Doc | Doc[]) => this.props.addDocument!(doc)}
-                        finishMarquee={this.finishMarquee}
                         docView={this.props.docViewPath().lastElement()}
-                        getPageFromScroll={this.getPageFromScroll}
+                        finishMarquee={this.finishMarquee}
                         savedAnnotations={this._savedAnnotations}
-                        annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
+                        annotationLayer={this._annotationLayer.current}
+                        mainCont={this._mainCont.current} />}
             </div>
         </div>;
     }
-- 
cgit v1.2.3-70-g09d2


From ef63a072a586f51e8fa51b4684987491287540b2 Mon Sep 17 00:00:00 2001
From: Naafiyan Ahmed <naafiyan@gmail.com>
Date: Thu, 7 Apr 2022 17:21:01 -0400
Subject: added grouping and write mode vs pen mode

---
 src/client/util/CurrentUserUtils.ts                |   4 +-
 src/client/views/GestureOverlay.tsx                |   5 +-
 .../views/collections/CollectionDockingView.tsx    |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  12 +--
 .../collections/collectionFreeForm/MarqueeView.tsx |   2 +-
 src/client/views/nodes/DocumentView.tsx            |   4 +-
 src/client/views/nodes/ImageBox.tsx                |   2 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   2 +-
 src/client/views/nodes/VideoBox.tsx                |   2 +-
 src/client/views/nodes/WebBox.tsx                  |   2 +-
 src/client/views/nodes/button/FontIconBox.tsx      | 112 ++++++++++-----------
 src/client/views/pdf/PDFViewer.tsx                 |   2 +-
 src/fields/InkField.ts                             |   3 +-
 webpack.config.js                                  |   2 +-
 14 files changed, 77 insertions(+), 79 deletions(-)

(limited to 'src/client/views/pdf/PDFViewer.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 6cab8c100..f08f13bbc 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1010,8 +1010,8 @@ export class CurrentUserUtils {
 
     static inkTools(doc: Doc) {
         const tools: Button[] = [
-            { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("pen", _readOnly_)' },
-            { title: "Mode", toolTip: "Mode (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("write", _readOnly_)' },
+            { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", click: 'setActiveInkTool("pen", _readOnly_)' },
+            { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("write", _readOnly_)' },
             { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", click: 'setActiveInkTool("eraser", _readOnly_)' },
             // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'setActiveInkTool("highlighter")' },
             { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'setActiveInkTool("circle", _readOnly_)' },
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 87b006a93..53b360e60 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -491,7 +491,10 @@ export class GestureOverlay extends Touchable {
 
     @action
     onPointerDown = (e: React.PointerEvent) => {
-        if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+        if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) {
+            if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
+                CurrentUserUtils.SelectedTool = InkTool.Write;
+            }
             this._points.push({ X: e.clientX, Y: e.clientY });
             setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
             // if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)");
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 9e8374605..c9f55a7bd 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -358,7 +358,7 @@ export class CollectionDockingView extends CollectionSubView() {
             }
         }
         if (!e.nativeEvent.cancelBubble && !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) &&
-            ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+            ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) {
             e.stopPropagation();
         }
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a694ca2b3..40164313c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -468,6 +468,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     case InkTool.Highlighter:
                         break;
                         // TODO: nda - this where we want to create the new "writingDoc" collection that we add strokes to
+                    case InkTool.Write:
+                        break;
                     case InkTool.Pen: 
                         // not the greatest solution
                         // want to have a currentInkDoc field
@@ -545,14 +547,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
                 const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), points,
                     { title: "ink stroke", x: B.x - ActiveInkWidth() / 2, y: B.y - ActiveInkWidth() / 2, _width: B.width + ActiveInkWidth(), _height: B.height + ActiveInkWidth() });
-                // const currentInkDoc = Cast(this.dataDoc.currentInkDoc, Doc, null);
-                // if (true) {
                     console.log("doc exists")
-                    // Doc.AddDocToList(currentInkDoc.data as Doc, undefined, inkDoc)
-                    this.unprocessedDocs.push(inkDoc);
-                    CollectionFreeFormView.collectionsWithUnprocessedInk.add(this);
+                    if (CurrentUserUtils.SelectedTool === InkTool.Write) {
+                        this.unprocessedDocs.push(inkDoc);
+                        CollectionFreeFormView.collectionsWithUnprocessedInk.add(this);
+                    }
                     this.addDocument(inkDoc);
-                // } else this.addDocument(inkDoc);
                 e.stopPropagation();
                 break;
             case GestureUtils.Gestures.Box:
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index eeb2b653b..94f2d17de 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -651,7 +651,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         return <div className="marqueeView"
             style={{
                 overflow: StrCast(this.props.Document._overflow),
-                cursor: CurrentUserUtils.SelectedTool === InkTool.Pen ? "crosshair" : "pointer"
+                cursor: CurrentUserUtils.SelectedTool === InkTool.Pen || InkTool.Write ? "crosshair" : "pointer"
             }}
 
             onDragOver={e => e.preventDefault()}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7092b335c..2e822bb6a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -517,7 +517,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     onPointerDown = (e: React.PointerEvent): void => {
         // continue if the event hasn't been canceled AND we are using a mouse or this has an onClick or onDragStart function (meaning it is a button document)
-        if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool))) {
+        if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool))) {
             if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
                 e.stopPropagation();
                 if (SelectionManager.IsSelected(this.props.DocumentView(), true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
@@ -550,7 +550,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     onPointerMove = (e: PointerEvent): void => {
         if (e.cancelBubble) return;
-        if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool))) return;
+        if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool))) return;
 
         if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 0a4168698..a8da22b61 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -322,7 +322,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         return <div className="imageBox-annotationLayer" style={{ height: this.props.PanelHeight() }} ref={this._annotationLayer} />;
     }
     marqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+        if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) {
             setupMoveUpEvents(this, e, action(e => {
                 MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
                 this._marqueeing = [e.clientX, e.clientY];
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index aa2130af5..9bf7c2e8c 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -471,7 +471,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     onMarqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) {
             setupMoveUpEvents(this, e, action(e => {
                 MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
                 this._marqueeing = [e.clientX, e.clientY];
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 33fdc4935..a5b8967c4 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -549,7 +549,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     }
 
     marqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+        if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) {
             setupMoveUpEvents(this, e, action(e => {
                 MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
                 this._marqueeing = [e.clientX, e.clientY];
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 7ff47107e..54c49ecad 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -529,7 +529,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     onMarqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) {
             setupMoveUpEvents(this, e, action(e => {
                 MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
                 this._marqueeing = [e.clientX, e.clientY];
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index fc0cc87ca..7f3a667a9 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -13,6 +13,7 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields
 import { WebField } from '../../../../fields/URLField';
 import { aggregateBounds, Utils } from '../../../../Utils';
 import { DocumentType } from '../../../documents/DocumentTypes';
+import { CurrentUserUtils } from '../../../util/CurrentUserUtils';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { ScriptingGlobals } from "../../../util/ScriptingGlobals";
 import { SelectionManager } from '../../../util/SelectionManager';
@@ -717,64 +718,56 @@ ScriptingGlobals.add(function setActiveInkTool(tool: string, checkResult?: boole
 
         // easy way without backing up to the server
         // 
-    CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
-        const selected = ffView.unprocessedDocs;
-        ffView._marqueeViewRef.current?.getCollection(ffView.unprocessedDocs, undefined, [], true);
-        // loop through selected an get the bound
-        const bounds: { x: number, y: number, width?: number, height?: number }[] = []
-        
-        selected.map(action(d => {
-            const x = NumCast(d.x);
-            const y = NumCast(d.y);
-            const width = d[WidthSym]();
-            const height = d[HeightSym]();
-            bounds.push({x, y, width, height});
-        }))
-
-        const aggregBounds = aggregateBounds(bounds, 0, 0);
-        const marqViewRef = ffView._marqueeViewRef.current;
-
-        // set the vals for bounds in marqueeView
-        if (marqViewRef) {
-            marqViewRef._downX = aggregBounds.x;
-            marqViewRef._downY = aggregBounds.y;
-            marqViewRef._lastX = aggregBounds.r;
-            marqViewRef._lastY = aggregBounds.b;
-        }        
-
-        selected.map(action(d => {
-            const dx = NumCast(d.x);
-            const dy = NumCast(d.y);
-            delete d.x;
-            delete d.y;
-            delete d.activeFrame;
-            delete d._timecodeToShow;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
-            delete d._timecodeToHide;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
-            // TODO: nda - actually calc the bounds
-            // get the bounds
-            // d.x = dx - aggregBounds.x;
-            // d.y = dy - aggregBounds.y;
+    if (CurrentUserUtils.SelectedTool === InkTool.Write) {
+        CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
+            const selected = ffView.unprocessedDocs;
+            // loop through selected an get the bound
+            const bounds: { x: number, y: number, width?: number, height?: number }[] = []
             
-            // d.x = dx - aggregBounds.x ;
-            // d.y = dy;
-            if (marqViewRef?.Bounds) {
-                d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds?.width / 2;
-                d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2;
-            }
-            console.log(d[DataSym], d.x, d.y)
-            return d;
-        }));
-        ffView.props.removeDocument?.(selected);
-        // TODO: nda - this is the code to actually get a new grouped collection
-        // const newCollection = ffView._marqueeViewRef.current?.getCollection(ffView.unprocessedDocs, undefined, [], true);
-        const newCollection = marqViewRef?.getCollection(selected, undefined, [], true);
-        console.log("newcoll:", newCollection?.[DataSym]);
-
-        // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
-        newCollection && ffView.props.addDocument?.(newCollection);
-        ffView.unprocessedDocs = [];
-    });
-
+            selected.map(action(d => {
+                const x = NumCast(d.x);
+                const y = NumCast(d.y);
+                const width = d[WidthSym]();
+                const height = d[HeightSym]();
+                bounds.push({x, y, width, height});
+            }))
+    
+            const aggregBounds = aggregateBounds(bounds, 0, 0);
+            const marqViewRef = ffView._marqueeViewRef.current;
+    
+            // set the vals for bounds in marqueeView
+            if (marqViewRef) {
+                marqViewRef._downX = aggregBounds.x;
+                marqViewRef._downY = aggregBounds.y;
+                marqViewRef._lastX = aggregBounds.r;
+                marqViewRef._lastY = aggregBounds.b;
+            }        
+    
+            selected.map(action(d => {
+                const dx = NumCast(d.x);
+                const dy = NumCast(d.y);
+                delete d.x;
+                delete d.y;
+                delete d.activeFrame;
+                delete d._timecodeToShow;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+                delete d._timecodeToHide;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+                // TODO: nda - actually calc the bounds
+                // calculate pos based on bounds
+                if (marqViewRef?.Bounds) {
+                    d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2;
+                    d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2;
+                }
+                return d;
+            }));
+            ffView.props.removeDocument?.(selected);
+            // TODO: nda - this is the code to actually get a new grouped collection
+            const newCollection = marqViewRef?.getCollection(selected, undefined, [], true);
+    
+            // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
+            newCollection && ffView.props.addDocument?.(newCollection);
+            ffView.unprocessedDocs = [];
+        });
+    }
     CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
 
     if (checkResult) {
@@ -793,8 +786,9 @@ ScriptingGlobals.add(function setActiveInkTool(tool: string, checkResult?: boole
         if (Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance.InkShape) {
             Doc.UserDoc().activeInkTool = InkTool.None;
         } else if (tool == "write") {
-            console.log("write mode selected - create groupDoc here!")
-            // 
+            console.log("write mode selected - create groupDoc here!", tool)
+            Doc.UserDoc().activeInkTool = tool;
+            GestureOverlay.Instance.InkShape = "";
         } else {
             Doc.UserDoc().activeInkTool = tool;
             GestureOverlay.Instance.InkShape = "";
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 1856c5353..37fbd7a1d 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -371,7 +371,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         if ((e.button !== 0 || e.altKey) && this.props.isContentActive(true)) {
             this._setPreviewCursor?.(e.clientX, e.clientY, true, false);
         }
-        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) {
             this.props.select(false);
             MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
             this._marqueeing = [e.clientX, e.clientY];
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index 31024e805..c34e8f93d 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -11,7 +11,8 @@ export enum InkTool {
     Pen = "pen",
     Highlighter = "highlighter",
     Eraser = "eraser",
-    Stamp = "stamp"
+    Stamp = "stamp",
+    Write = "write"
 }
 
 
diff --git a/webpack.config.js b/webpack.config.js
index 3fd00bcf3..5a954db19 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -49,7 +49,7 @@ module.exports = {
         repl: ["./src/debug/Repl.tsx", 'webpack-hot-middleware/client?reload=true'],
         test: ["./src/debug/Test.tsx", 'webpack-hot-middleware/client?reload=true'],
         inkControls: ["./src/mobile/InkControls.tsx", 'webpack-hot-middleware/client?reload=true'],
-        mobileInterface: ["./src/mobile/MobileMain.tsx", 'webpack-hot-middleware/client?reload=true'],
+        mobileInterface: ["./src/client/views/Main.tsx", 'webpack-hot-middleware/client?reload=true'],
     },
     devtool: "source-map",
     output: {
-- 
cgit v1.2.3-70-g09d2


From ec4cf9ea0439701fde3aa49e462b054998c657f1 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 11 Apr 2022 17:57:37 -0400
Subject: got pdf's to generate icons when iconifying.

---
 src/client/util/CurrentUserUtils.ts       | 20 ++++++++++-
 src/client/views/DocumentDecorations.scss |  5 +--
 src/client/views/nodes/PDFBox.tsx         | 57 +++++++++++++++++++++++++++++--
 src/client/views/nodes/VideoBox.scss      |  1 +
 src/client/views/nodes/VideoBox.tsx       | 20 +++++++----
 src/client/views/pdf/PDFViewer.tsx        |  2 +-
 6 files changed, 92 insertions(+), 13 deletions(-)

(limited to 'src/client/views/pdf/PDFViewer.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 6d8e2d30c..0e392cc85 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -334,9 +334,27 @@ export class CurrentUserUtils {
             proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png");
             doc["template-icon-view-col"] = new PrefetchProxy(iconColView);
         }
+        if (doc["template-icon-view-video"] === undefined) {
+            const iconVidView = Docs.Create.ImageDocument("", { title: "icon", _width: 360 / 4, _height: 270 / 4, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true });
+            iconVidView.isTemplateDoc = makeTemplate(iconVidView, true, "icon_" + DocumentType.VID);
+            const proto = iconVidView.proto as Doc;
+            proto["icon-nativeWidth"] = 360 / 4;
+            proto["icon-nativeHeight"] = 270 / 4;
+            proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png");
+            doc["template-icon-view-video"] = new PrefetchProxy(iconVidView);
+        }
+        if (doc["template-icon-view-pdf"] === undefined) {
+            const iconPdfView = Docs.Create.ImageDocument("", { title: "icon", _width: 360 / 4, _height: 270 / 4, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true });
+            iconPdfView.isTemplateDoc = makeTemplate(iconPdfView, true, "icon_" + DocumentType.PDF);
+            const proto = iconPdfView.proto as Doc;
+            proto["icon-nativeWidth"] = 360 / 4;
+            proto["icon-nativeHeight"] = 270 / 4;
+            proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png");
+            doc["template-icon-view-pdf"] = new PrefetchProxy(iconPdfView);
+        }
         if (doc["template-icons"] === undefined) {
             doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc,
-            doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75, system: true }));
+            doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-video"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75, system: true }));
         } else {
             const templateIconsDoc = Cast(doc["template-icons"], Doc, null);
             const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc,
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 35e37a2cd..7432d45bf 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -232,12 +232,13 @@ $linkGap: 3px;
         opacity: 1;
         width: 100%;
         grid-column: 2;
+        grid-column-end: 2;
         pointer-events: auto;
         overflow: hidden;
         text-align: center;
         display: flex;
-        padding-left: 5px;
-        padding-right: 12px;
+        padding-left: 2px;
+        padding-right: 2px;
         height: 20px;
         position: absolute;
         border-radius: 8px;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index a2e7d2aa3..3f1771e68 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -2,10 +2,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
+import { CreateImage } from "../nodes/WebBoxRenderer";
 import "pdfjs-dist/web/pdf_viewer.css";
-import { Doc, DocListCast, Opt, WidthSym } from "../../../fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
 import { Cast, NumCast, StrCast, ImageCast } from '../../../fields/Types';
-import { PdfField } from "../../../fields/URLField";
+import { ImageField, PdfField } from "../../../fields/URLField";
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
@@ -21,6 +22,8 @@ import { SidebarAnnos } from '../SidebarAnnos';
 import { FieldView, FieldViewProps } from './FieldView';
 import "./PDFBox.scss";
 import React = require("react");
+import { Id } from '../../../fields/FieldSymbols';
+import { VideoBox } from './VideoBox';
 
 @observer
 export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
@@ -52,6 +55,56 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     }
 
+    replaceCanvases = (oldDiv: HTMLElement, newDiv: HTMLElement) => {
+        if (oldDiv.childNodes) {
+            for (let i = 0; i < oldDiv.childNodes.length; i++) {
+                this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement);
+            }
+        }
+        if (oldDiv instanceof HTMLCanvasElement) {
+            const canvas = oldDiv as HTMLCanvasElement;
+            var img = document.createElement('img'); // create a Image Element
+            img.src = canvas.toDataURL();   //image source
+            img.style.width = canvas.style.width;
+            img.style.height = canvas.style.height;
+            const newCan = newDiv as HTMLCanvasElement;
+            const parEle = newCan.parentElement as HTMLElement;
+            parEle.removeChild(newCan);
+            parEle.appendChild(img);
+        }
+    }
+
+    updateIcon = () => {
+        const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
+        const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
+        newDiv.style.width = (this.layoutDoc[WidthSym]()).toString();
+        newDiv.style.height = (this.layoutDoc[HeightSym]()).toString();
+        this.replaceCanvases(docViewContent, newDiv)
+        var htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv);
+        const nativeWidth = this.layoutDoc[WidthSym]();
+        const nativeHeight = this.layoutDoc[HeightSym]();
+
+        CreateImage(
+            "",
+            document.styleSheets,
+            htmlString?.replace(/docView-hack/g, 'documentView-hack'),
+            nativeWidth,
+            nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(),
+            NumCast(this.layoutDoc._scrollTop) * this.props.PanelHeight() / NumCast(this.rootDoc[this.fieldKey + "-nativeHeight"])
+        ).then
+            ((data_url: any) => {
+                VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true).then(
+                    returnedfilename => setTimeout(action(() => {
+                        this.dataDoc.icon = new ImageField(returnedfilename);
+                        this.dataDoc["icon-nativeWidth"] = nativeWidth;
+                        this.dataDoc["icon-nativeHeight"] = nativeHeight;
+                    }), 500));
+            })
+            .catch(function (error: any) {
+                console.error('oops, something went wrong!', error);
+            });
+    }
+
     componentWillUnmount() { this._selectReactionDisposer?.(); }
     componentDidMount() {
         this.props.setContentView?.(this);
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index 3cf10a033..f47a71469 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -81,6 +81,7 @@
     align-items: center;
     justify-content: center;
     display: flex;
+    width: 100%;
     visibility: none;
     opacity: 0;
     background-color: $dark-gray;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index ca8dc8515..b7b8ce064 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -4,10 +4,10 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
 import { observer } from "mobx-react";
 import { basename } from "path";
 import * as rp from 'request-promise';
-import { Doc, DocListCast } from "../../../fields/Doc";
+import { Doc, DocListCast, HeightSym, WidthSym } from "../../../fields/Doc";
 import { InkTool } from "../../../fields/InkField";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { AudioField, VideoField } from "../../../fields/URLField";
+import { AudioField, ImageField, VideoField } from "../../../fields/URLField";
 import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DocumentType } from "../../documents/DocumentTypes";
@@ -220,16 +220,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
 
     // creates and links snapshot photo of current video frame
-    @action public Snapshot(downX?: number, downY?: number) {
+    @action public Snapshot = (downX?: number, downY?: number, cb?: (filename: string, x: number | undefined, y: number | undefined) => void) => {
         const width = NumCast(this.layoutDoc._width);
         const canvas = document.createElement('canvas');
         canvas.width = 640;
         canvas.height = 640 * Doc.NativeHeight(this.layoutDoc) / (Doc.NativeWidth(this.layoutDoc) || 1);
         const ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
         if (ctx) {
-            // ctx.rect(0, 0, canvas.width, canvas.height);
-            // ctx.fillStyle = "blue";
-            // ctx.fill();
             this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height);
         }
 
@@ -259,10 +256,19 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             const encodedFilename = encodeURIComponent("snapshot" + retitled + "_" + (this.layoutDoc._currentTimecode || 0).toString().replace(/\./, "_"));
             const filename = basename(encodedFilename);
             VideoBox.convertDataUri(dataUrl, filename).then((returnedFilename: string) =>
-                returnedFilename && this.createRealSummaryLink(returnedFilename, downX, downY));
+                returnedFilename && (cb ?? this.createRealSummaryLink)(returnedFilename, downX, downY));
         }
     }
 
+    updateIcon = () => {
+        const makeIcon = (returnedfilename: string) => {
+            this.dataDoc.icon = new ImageField(returnedfilename);
+            this.dataDoc["icon-nativeWidth"] = this.layoutDoc[WidthSym]();
+            this.dataDoc["icon-nativeHeight"] = this.layoutDoc[HeightSym]();
+        }
+        this.Snapshot(undefined, undefined, makeIcon);
+    }
+
     // creates link for snapshot
     createRealSummaryLink = (imagePath: string, downX?: number, downY?: number) => {
         const url = !imagePath.startsWith("/") ? Utils.CorsProxy(imagePath) : imagePath;
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 651b0401b..9aaa6e90f 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -74,7 +74,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
-    private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+    _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     private _selectionText: string = "";
     private _downX: number = 0;
     private _downY: number = 0;
-- 
cgit v1.2.3-70-g09d2


From cb59842fcb79af7ef72443e08c6a479c3f0af343 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 25 Apr 2022 20:20:48 -0400
Subject:  changed freeformview's with engines to not allow children to be
 edited using setHeight prop since engine layouts are temporary and shouldn't
 edit the doc.  fixed tags to work with pivot view. cleaned up pile views to
 be less hacky..

---
 src/client/documents/Documents.ts                  | 27 +++++++++++++---------
 src/client/views/MainView.tsx                      |  1 +
 src/client/views/SidebarAnnos.tsx                  |  2 +-
 .../views/collections/CollectionPileView.tsx       |  4 ++--
 .../views/collections/CollectionStackingView.tsx   |  6 ++---
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/collections/TreeView.tsx          |  1 +
 .../CollectionFreeFormLayoutEngines.tsx            | 26 +++++++++++++--------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +++-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  5 +---
 src/client/views/nodes/DocumentContentsView.tsx    |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 17 +++++++-------
 src/client/views/nodes/FieldView.tsx               |  2 +-
 src/client/views/nodes/LinkDocPreview.tsx          |  1 +
 src/client/views/nodes/WebBox.tsx                  |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 src/client/views/pdf/PDFViewer.tsx                 |  2 +-
 src/fields/Doc.ts                                  |  2 +-
 19 files changed, 62 insertions(+), 48 deletions(-)

(limited to 'src/client/views/pdf/PDFViewer.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 49e53a214..a19530c92 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -14,7 +14,7 @@ import { Cast, NumCast, StrCast } from "../../fields/Types";
 import { AudioField, ImageField, MapField, PdfField, VideoField, WebField, YoutubeField } from "../../fields/URLField";
 import { SharingPermissions } from "../../fields/util";
 import { Upload } from "../../server/SharedMediaTypes";
-import { OmitKeys, Utils } from "../../Utils";
+import { OmitKeys, Utils, aggregateBounds } from "../../Utils";
 import { YoutubeBox } from "../apis/youtube/YoutubeBox";
 import { DocServer } from "../DocServer";
 import { Networking } from "../Network";
@@ -1352,26 +1352,31 @@ export namespace DocUtils {
         if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", "");
     }
 
-    export function pileup(docList: Doc[], x?: number, y?: number, create: boolean = true) {
+    export function pileup(docList: Doc[], x?: number, y?: number, size: number = 55, create: boolean = true) {
         let w = 0, h = 0;
         runInAction(() => {
             docList.forEach(d => {
                 DocUtils.iconify(d);
-                w = Math.max(d[WidthSym](), w);
-                h = Math.max(d[HeightSym](), h);
+                w = Math.max(NumCast(d._width), w);
+                h = Math.max(NumCast(d._height), h);
             });
-            h = Math.max(h, w * 4 / 3); // converting to an icon does not update the height right away.  so this is a fallback hack to try to do something reasonable
             docList.forEach((d, i) => {
-                d.x = Math.cos(Math.PI * 2 * i / docList.length) * 10 - w / 2;
-                d.y = Math.sin(Math.PI * 2 * i / docList.length) * 10 - h / 2;
+                d.x = Math.cos(Math.PI * 2 * i / docList.length) * size;
+                d.y = Math.sin(Math.PI * 2 * i / docList.length) * size;
+                d._timecodeToShow = undefined;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+            });
+            const aggBounds = aggregateBounds(docList.map(d => ({ x: NumCast(d.x), y: NumCast(d.y), width: NumCast(d._width), height: NumCast(d._height) })), 0, 0);
+            docList.forEach((d, i) => {
+                d.x = NumCast(d.x) - ((aggBounds.r + aggBounds.x) / 2);
+                d.y = NumCast(d.y) - ((aggBounds.b + aggBounds.y) / 2);
                 d._timecodeToShow = undefined;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
             });
         });
         if (create) {
-            const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: (x || 0) - 55, y: (y || 0) - 55, _width: 110, _height: 100, });
-            newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55;
-            newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
-            newCollection._width = newCollection._height = 110;
+            const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2, });
+            newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - size;
+            newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - size;
+            newCollection._width = newCollection._height = size * 2;
             newCollection._jitterRotation = 10;
             return newCollection;
         }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a67cb3014..68b4710ed 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -297,6 +297,7 @@ export class MainView extends React.Component {
             searchFilterDocs={returnEmptyDoclist}
             ContainingCollectionView={undefined}
             ContainingCollectionDoc={undefined}
+            suppressSetHeight={true}
             renderDepth={-1}
         />;
     }
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index fae385660..43a02d029 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -88,7 +88,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
     removeDocument = (doc: Doc | Doc[]) => this.props.removeDocument(doc, this.sidebarKey);
     docFilters = () => [...StrListCast(this.props.layoutDoc._docFilters), ...StrListCast(this.props.layoutDoc[this.filtersKey])];
     showTitle = () => "title";
-    setHeightCallback = (height: number) => this.props.setHeight(height + this.filtersHeight());
+    setHeightCallback = (height: number) => this.props.setHeight?.(height + this.filtersHeight());
     render() {
         const renderTag = (tag: string) => {
             const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${tag}:check`);
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 8832b0f4a..4489601db 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -74,13 +74,13 @@ export class CollectionPileView extends CollectionSubView() {
             this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2;
             this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize);
             this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize);
-            DocUtils.pileup(this.childDocs, undefined, undefined, false);
+            DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false);
             this.layoutDoc._panX = 0;
             this.layoutDoc._panY = -10;
             this.props.Document._pileLayoutEngine = 'pass';
         } else {
             const defaultSize = 25;
-            !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 500);
+            !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 250);
             !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5);
             if (this.layoutEngine() === 'pass') {
                 this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 8634ea139..509005b45 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -141,7 +141,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             () => this.layoutDoc._columnHeaders = new List()
         );
         this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight,
-            autoHeight => autoHeight && this.props.setHeight(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
+            autoHeight => autoHeight && this.props.setHeight?.(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
                 this.headerMargin + (this.isStackingView ?
                     Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))) :
                     this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0)))));
@@ -408,7 +408,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                                 Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
                                     Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))));
                             if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) {
-                                this.props.setHeight(height);
+                                this.props.setHeight?.(height);
                             }
                         }
                     }));
@@ -458,7 +458,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                     this.observer = new _global.ResizeObserver(action((entries: any) => {
                         if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
                             const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0);
-                            this.props.setHeight(this.headerMargin + height);
+                            this.props.setHeight?.(this.headerMargin + height);
                         }
                     }));
                     this.observer.observe(ref);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 5bdd0c0ac..30e0adf24 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -453,7 +453,7 @@ export function CollectionSubView<X>(moreProps?: X) {
                 if (completed) completed(set);
                 else {
                     if (isFreeformView && generatedDocuments.length > 1) {
-                        addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!);
+                        addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!,);
                     } else {
                         generatedDocuments.forEach(addDocument);
                     }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 76da96174..41970eb96 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -88,7 +88,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             const titleHeight = !this._titleRef ? this.marginTop() : Number(getComputedStyle(this._titleRef).height.replace("px", ""));
             const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), this.marginBot());
             this.layoutDoc._autoHeightMargins = bodyHeight;
-            this.props.setHeight(bodyHeight + titleHeight);
+            this.props.setHeight?.(bodyHeight + titleHeight);
         }
     }
     unobserveHeight = (ref: any) => {
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 6213850d8..b9710b3f5 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -30,6 +30,7 @@ import { CollectionTreeView } from './CollectionTreeView';
 import { CollectionView, CollectionViewType } from './CollectionView';
 import "./TreeView.scss";
 import React = require("react");
+import { KeyValueBox } from '../nodes/KeyValueBox';
 
 export interface TreeViewProps {
     treeView: CollectionTreeView;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 598960af7..16c7df311 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -111,17 +111,17 @@ export function computerStarburstLayout(
     viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[],
     engineProps: any
 ) {
-    const mustFit = pivotDoc[WidthSym]() !== panelDim[0];
+    const mustFit = pivotDoc[WidthSym]() !== panelDim[0]; // if a panel size is set that's not the same as the pivot doc's size, then assume this is in a panel for a content fitting view (like a grid) in which case everything must be scaled to stay within the panel
     const docMap = new Map<string, PoolData>();
-    const burstRadius = mustFit ? panelDim : [NumCast(pivotDoc._starburstRadius, panelDim[0]), NumCast(pivotDoc._starburstRadius, panelDim[1])];
-    const docSize = mustFit ? panelDim[0] * .33 : 75; // assume a icon sized at 75
-    const scaleDim = [burstRadius[0] + docSize, burstRadius[1] + docSize];
+    const docSize = mustFit ? panelDim[0] * .33 : 75; // assume an icon sized at 75
+    const burstRadius = mustFit ? panelDim : [NumCast(pivotDoc._starburstRadius, panelDim[0]) - docSize, NumCast(pivotDoc._starburstRadius, panelDim[1]) - docSize];
+    const scaleDim = [burstRadius[0] * 2 + docSize, burstRadius[1] * 2 + docSize];
     childPairs.forEach(({ layout, data }, i) => {
-        const docSize = layout.layoutKey === "layout_icon" ? mustFit ? panelDim[0] * .33 : 75 : 400; // assume a icon sized at 10750
+        const docSize = layout.layoutKey === "layout_icon" ? (mustFit ? panelDim[0] * .33 : 75) : 400; // assume a icon sized at 75
         const deg = i / childPairs.length * Math.PI * 2;
         docMap.set(layout[Id], {
-            x: Math.cos(deg) * (burstRadius[0] / 3) - docSize / 2,
-            y: Math.sin(deg) * (burstRadius[1] / 3) - docSize * layout[HeightSym]() / layout[WidthSym]() / 2,
+            x: Math.cos(deg) * burstRadius[0] - docSize / 2,
+            y: Math.sin(deg) * burstRadius[1] - docSize * layout[HeightSym]() / layout[WidthSym]() / 2,
             width: docSize,//layout[WidthSym](),
             height: docSize * layout[HeightSym]() / layout[WidthSym](),
             zIndex: NumCast(layout.zIndex),
@@ -129,7 +129,7 @@ export function computerStarburstLayout(
             replica: ""
         });
     });
-    const divider = { type: "div", color: "transparent", x: -burstRadius[0] / 3, y: 0, width: 15, height: 15, payload: undefined };
+    const divider = { type: "div", color: "transparent", x: -burstRadius[0], y: 0, width: 15, height: 15, payload: undefined };
     return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]);
 }
 
@@ -267,7 +267,13 @@ export function computePivotLayout(
     });
 
     const dividers = sortedPivotKeys.map((key, i) =>
-        ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters }));
+    ({
+        type: "div", color: "lightGray",
+        x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2,
+        y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander,
+        height: maxColHeight,
+        payload: pivotColumnGroups.get(key)!.filters
+    }));
     groupNames.push(...dividers);
     return normalizeResults(panelDim, max_text, docMap, poolData, viewDefsToJSX, groupNames, 0, []);
 }
@@ -404,7 +410,7 @@ function normalizeResults(
     const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
     const docEles = Array.from(docMap.entries()).map(ele => ele[1]);
     const aggBounds = aggregateBounds(extras.concat(grpEles.concat(docEles.map(de => ({ ...de, type: "doc", payload: "" })))).filter(e => e.zIndex !== -99), 0, 0);
-    aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x);
+    aggBounds.r = aggBounds.x + Math.max(minWidth, aggBounds.r - aggBounds.x);
     const wscale = panelDim[0] / (aggBounds.r - aggBounds.x);
     let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale;
     if (Number.isNaN(scale)) scale = 1;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index eaef1b49c..a14405a0b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1158,6 +1158,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             replica={entry.replica}
             renderIndex={renderIndex}
             dataTransition={entry.transition}
+            suppressSetHeight={this.layoutEngine ? true : false}
             renderCutoffProvider={this.renderCutoffProvider}
             ContainingCollectionView={this.props.CollectionView}
             ContainingCollectionDoc={this.props.Document}
@@ -1300,10 +1301,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return [] as ViewDefResult[];
     }
 
+    @computed get layoutEngine() { return this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine); }
     @computed get doInternalLayoutComputation() {
         TraceMobx();
         const newPool = new Map<string, PoolData>();
-        switch (this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine)) {
+        switch (this.layoutEngine) {
             case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) };
             case "timeline": return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
             case "pivot": return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 3e35039e3..6097425e1 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -17,9 +17,6 @@ import { StyleProp } from "../StyleProvider";
 import "./CollectionFreeFormDocumentView.scss";
 import { DocumentView, DocumentViewProps } from "./DocumentView";
 import React = require("react");
-import { DocumentType } from "../../documents/DocumentTypes";
-import { collectionSchema } from "../../../fields/documentSchemas";
-import { CollectionViewType } from "../collections/CollectionView";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined;
@@ -174,7 +171,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             style={{
                 outline: this.Highlight ? "orange solid 2px" : "",
                 width: this.panelWidth(),
-                height: !this.ShowTitle && this.layoutDoc.autoHeight && this.rootDoc.type === DocumentType.RTF ? undefined : this.panelHeight(),
+                height: this.panelHeight(),
                 transform: this.transform,
                 transition: this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition),
                 zIndex: this.ZInd,
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 005133eb0..d4e8ffc7f 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -112,7 +112,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
     isSelected: (outsideReaction: boolean) => boolean,
     select: (ctrl: boolean) => void,
     scaling?: () => number,
-    setHeight: (height: number) => void,
+    setHeight?: (height: number) => void,
     layoutKey: string,
 }> {
     @computed get layout(): string {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 504a3afb6..20eabe6a9 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -114,6 +114,7 @@ export interface DocumentViewSharedProps {
     fitContentsToDoc?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document
     ContainingCollectionView: Opt<CollectionView>;
     ContainingCollectionDoc: Opt<Doc>;
+    suppressSetHeight?: boolean;
     thumbShown?: () => boolean;
     isHovering?: () => boolean;
     setContentView?: (view: DocComponentView) => any;
@@ -842,11 +843,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
     contentScaling = () => this.ContentScale;
     onClickFunc = () => this.onClickHandler;
-    setHeight = (height: number) => {
-        if (this.props.renderDepth !== -1) {
-            this.layoutDoc._height = height;
-        }
-    }
+    setHeight = (height: number) => this.layoutDoc._height = height;
     setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view);
     isContentActive = (outsideReaction?: boolean) => {
         return this.props.isContentActive() === false ? false : (
@@ -890,7 +887,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 setContentView={this.setContentView}
                 scaling={this.contentScaling}
                 PanelHeight={this.panelHeight}
-                setHeight={this.setHeight}
+                setHeight={!this.props.suppressSetHeight ? this.setHeight : undefined}
                 isContentActive={this.isContentActive}
                 ScreenToLocalTransform={this.screenToLocal}
                 rootSelected={this.rootSelected}
@@ -1207,7 +1204,11 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return this.docView?._componentView?.reverseNativeScaling?.() ? 0 :
             returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions));
     }
-    @computed get shouldNotScale() { return (this.fitWidth && !this.nativeWidth) || this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any); }
+    @computed get shouldNotScale() {
+        return (this.fitWidth && !this.nativeWidth) ||
+            this.props.ContainingCollectionView?.collectionViewType === CollectionViewType.Time ||
+            this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any);
+    }
     @computed get effectiveNativeWidth() { return this.shouldNotScale ? 0 : (this.nativeWidth || NumCast(this.layoutDoc.width)); }
     @computed get effectiveNativeHeight() { return this.shouldNotScale ? 0 : (this.nativeHeight || NumCast(this.layoutDoc.height)); }
     @computed get nativeScaling() {
@@ -1324,7 +1325,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                         position: this.props.Document.isInkMask ? "absolute" : undefined,
                         transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
                         width: isButton || isPresTreeElement ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
-                        height: (!this.props.ignoreAutoHeight && this.layoutDoc.autoHeight && this.layoutDoc.type === DocumentType.RTF) || isButton || this.props.forceAutoHeight ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` :
+                        height: isButton || this.props.forceAutoHeight ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` :
                             `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`),
                     }}>
                     <DocumentViewInternal {...this.props}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 943b9f153..ae9acfe3f 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -22,7 +22,7 @@ export interface FieldViewProps extends DocumentViewSharedProps {
     isDocumentActive?: () => boolean;
     isSelected: (outsideReaction?: boolean) => boolean;
     scaling?: () => number;
-    setHeight: (height: number) => void;
+    setHeight?: (height: number) => void;
 
     // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
     pointerEvents?: string;
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index eca2e3cfd..55f6d6059 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -195,6 +195,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                             ContainingCollectionDoc={undefined}
                             ContainingCollectionView={undefined}
                             renderDepth={-1}
+                            suppressSetHeight={true}
                             PanelWidth={this.width}
                             PanelHeight={this.height}
                             focus={DocUtils.DefaultFocus}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 0fd193977..eeb7b925b 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -164,7 +164,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             autoHeight => {
                 if (autoHeight) {
                     this.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]);
-                    this.props.setHeight(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1));
+                    this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1));
                 }
             });
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index dc1c67bea..710270be4 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1524,7 +1524,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         if (children) {
             const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
             const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
-            if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
+            if (this.props.setHeight && scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
                 const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight;
                 if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) {
                     setScrollHeight();
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 9aaa6e90f..92a69c02b 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -123,7 +123,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
             autoHeight => {
                 if (autoHeight) {
                     this.props.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]);
-                    this.props.setHeight(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1));
+                    this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1));
                 }
             });
 
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 15df673f3..63af8401c 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1108,7 +1108,7 @@ export namespace Doc {
         if (typeof value === "string") {
             value = value.replace(`,${Utils.noRecursionHack}`, "");
         }
-        const fieldVal = doc[key];
+        const fieldVal = key === "#" ? (StrCast(doc.tags).includes(":#" + value + ":") ? StrCast(doc.tags) : undefined) : doc[key];
         if (Cast(fieldVal, listSpec("string"), []).length) {
             const vals = Cast(fieldVal, listSpec("string"), []);
             const docs = vals.some(v => (v as any) instanceof Doc);
-- 
cgit v1.2.3-70-g09d2


From 40ccb702d1dddf0ce9f15e047feb321353f01580 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 27 Apr 2022 14:55:04 -0400
Subject: fixed following pushpin links to text selections to toggle the text
 box (since markers don't have a hidden flag for toggling).  fixed pdf
 next/prev page and # pages.  fixed pdf selections to not include <br>'s which
 apparently have the wrong bounds.

---
 src/client/util/DocumentManager.ts      | 11 ++++++-----
 src/client/views/nodes/DocumentView.tsx |  2 +-
 src/client/views/nodes/PDFBox.tsx       | 10 ++++++++--
 src/client/views/pdf/PDFViewer.tsx      |  8 +++-----
 4 files changed, 18 insertions(+), 13 deletions(-)

(limited to 'src/client/views/pdf/PDFViewer.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 607a3d6bf..cc3196baa 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -168,15 +168,17 @@ export class DocumentManager {
         originalTarget = originalTarget ?? targetDoc;
         const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
         const docView = getFirstDocView(targetDoc, originatingDoc);
-        const wasHidden = targetDoc.hidden;  // 
+        const annotatedDoc = Cast(targetDoc.annotationOn, Doc, null);
+        const resolvedTarget = targetDoc.type === DocumentType.MARKER ? annotatedDoc ?? targetDoc : targetDoc; // if target is a marker, then focus toggling should apply to the document it's on since the marker itself doesn't have a hidden field
+        const wasHidden = resolvedTarget.hidden;
         if (wasHidden) {
             runInAction(() => {
-                targetDoc.hidden = false;
-                docView?.props.bringToFront(targetDoc);
+                resolvedTarget.hidden = false;
+                docView?.props.bringToFront(resolvedTarget);
             });  // if the target is hidden, un-hide it here.
         }
         const focusAndFinish = (didFocus: boolean) => {
-            const finalTargetDoc = docView?.Document ?? targetDoc;
+            const finalTargetDoc = docView?.Document ?? resolvedTarget;
             if (originatingDoc?.isPushpin) {
                 if (!didFocus && !wasHidden) {  // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal
                     finalTargetDoc.hidden = !finalTargetDoc.hidden;
@@ -188,7 +190,6 @@ export class DocumentManager {
             finished?.();
             return false;
         };
-        const annotatedDoc = Cast(targetDoc.annotationOn, Doc, null);
         const annoContainerView = annotatedDoc && getFirstDocView(annotatedDoc);
         const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
         const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext : undefined;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9d738180b..88000ecbd 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -460,7 +460,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         // after  a timeout, the right _componentView should have been created, so call it to update its view spec values
         setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false));
         const focusSpeed = this._componentView?.scrollFocus?.(anchor, !options?.instant || !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
-        const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing;
+        const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing;
         this.props.focus(options?.docTransform ? anchor : this.rootDoc, {
             ...options, afterFocus: (didFocus: boolean) =>
                 new Promise<ViewAdjustment>(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : ViewAdjustment.doNothing), focusSpeed ?? 0))
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 23749d479..b556b0ef1 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -157,8 +157,14 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     });
     public prevAnnotation = () => this._pdfViewer?.prevAnnotation();
     public nextAnnotation = () => this._pdfViewer?.nextAnnotation();
-    public backPage = () => { this.Document._curPage = (NumCast(this.Document._curPage) || 1) - 1; return true; };
-    public forwardPage = () => { this.Document._curPage = (NumCast(this.Document._curPage) || 1) + 1; return true; };
+    public backPage = () => {
+        this.Document._curPage = Math.max(1, (NumCast(this.Document._curPage) || 1) - 1);
+        return true;
+    };
+    public forwardPage = () => {
+        this.Document._curPage = Math.min(NumCast(this.dataDoc[this.props.fieldKey + "-numPages"]), (NumCast(this.Document._curPage) || 1) + 1);
+        return true;
+    };
     public gotoPage = (p: number) => this.Document._curPage = p;
 
     @undoBatch
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 92a69c02b..b58966173 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -167,7 +167,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                     });
                     if (i === this.props.pdf.numPages - 1) {
                         this.props.loaded?.(page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1],
-                            page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0], i);
+                            page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0], this.props.pdf.numPages);
                     }
                 }))));
             this.props.Document.scrollHeight = this._pageSizes.reduce((size, page) => size + page.height, 0) * 96 / 72;
@@ -299,9 +299,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     @action
     gotoPage = (p: number) => {
-        if (this._pdfViewer?._setDocumentViewerElement?.offsetParent) {
-            this._pdfViewer?.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) });
-        }
+        this._pdfViewer?.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) });
     }
 
     @action
@@ -419,7 +417,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
             const clientRects = selRange.getClientRects();
             for (let i = 0; i < clientRects.length; i++) {
                 const rect = clientRects.item(i);
-                if (rect && rect.width !== this._mainCont.current.clientWidth) {
+                if (rect && rect.width !== this._mainCont.current.clientWidth && rect.width) {
                     const scaleX = this._mainCont.current.offsetWidth / boundingRect.width;
                     const annoBox = document.createElement("div");
                     annoBox.className = "marqueeAnnotator-annotationBox";
-- 
cgit v1.2.3-70-g09d2


From d3eac62cbc55f186d9e2d3cc47407087fdc03ec7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 28 Apr 2022 00:21:27 -0400
Subject: fixed pushpin behavior when target is near top or bottom of
 scrollable document.

---
 src/Utils.ts                       | 6 +++---
 src/client/views/nodes/WebBox.tsx  | 2 +-
 src/client/views/pdf/PDFViewer.tsx | 2 +-
 3 files changed, 5 insertions(+), 5 deletions(-)

(limited to 'src/client/views/pdf/PDFViewer.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index 38eb51529..205f9379e 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -204,11 +204,11 @@ export namespace Utils {
         return { h: h, s: s, l: l };
     }
 
-    export function scrollIntoView(targetY: number, targetHgt: number, scrollTop: number, contextHgt: number, minSpacing: number) {
-        if (scrollTop + contextHgt < targetY + minSpacing + targetHgt) {
+    export function scrollIntoView(targetY: number, targetHgt: number, scrollTop: number, contextHgt: number, minSpacing: number, scrollHeight: number) {
+        if (scrollTop + contextHgt < Math.min(scrollHeight, targetY + minSpacing + targetHgt)) {
             return Math.ceil(targetY + minSpacing + targetHgt - contextHgt);
         }
-        if (scrollTop > targetY - minSpacing - targetHgt) {
+        if (scrollTop > Math.max(0, targetY - minSpacing - targetHgt)) {
             return Math.max(0, Math.floor(targetY - minSpacing - targetHgt));
         }
     }
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 6a3c6336d..5a39123b9 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -243,7 +243,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
         if (doc !== this.rootDoc && this._outerRef.current) {
             const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
-            const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * .1);
+            const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * .1, this.getScrollHeight());
             if (scrollTo !== undefined) {
                 const focusSpeed = smooth ? 500 : 0;
                 this._initialScroll !== undefined && (this._initialScroll = scrollTo);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index b58966173..b55b2cf44 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -181,7 +181,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         let focusSpeed: Opt<number>;
         if (doc !== this.props.rootDoc && mainCont) {
             const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
-            const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, .1 * windowHeight);
+            const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, .1 * windowHeight, NumCast(this.props.Document.scrollHeight));
             if (scrollTo !== undefined) {
                 focusSpeed = 500;
 
-- 
cgit v1.2.3-70-g09d2


From a2df645d2ffe83be24c20997c26eaf7257e847c0 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 2 May 2022 18:09:22 -0400
Subject: fixed scaling  ink label in lightbox,etc views. changed ink strokes
 so that clicking on label of closed stroke is lower preceden than clicking on
 line.  fixed fontsize UI for setting ink label sizes.  can no longer slide
 ink pts that are cusps along curve.

---
 src/client/views/InkStrokeProperties.ts            | 61 +++++++++++-----------
 src/client/views/InkingStroke.tsx                  | 44 +++++++++++-----
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  2 +-
 src/client/views/nodes/ImageBox.tsx                |  3 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |  4 +-
 src/client/views/nodes/VideoBox.tsx                |  3 +-
 src/client/views/nodes/button/FontIconBox.tsx      |  2 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     | 19 ++++---
 src/client/views/pdf/PDFViewer.tsx                 |  3 +-
 9 files changed, 84 insertions(+), 57 deletions(-)

(limited to 'src/client/views/pdf/PDFViewer.tsx')

diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index e4486fc78..922f309d7 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -230,42 +230,41 @@ export class InkStrokeProperties {
         this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
             const order = controlIndex % 4;
             const closed = InkingStroke.IsClosed(ink);
-            if (origInk && this._currentPoint > 0 && this._currentPoint < ink.length - 1) {
+            const brokenIndices = Cast(inkView.props.Document.brokenInkIndices, listSpec("number"), []);
+            if (origInk && this._currentPoint > 0 && this._currentPoint < ink.length - 1 && brokenIndices.findIndex(value => value === controlIndex) === -1) {
                 const cpt_before = ink[controlIndex];
                 const cpt = { X: cpt_before.X + deltaX, Y: cpt_before.Y + deltaY };
-                if (true) {
-                    const newink = origInk.slice();
-                    const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4;
-                    const splicedPoints = origInk.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8));
-                    const { nearestT, nearestSeg } = InkStrokeProperties.nearestPtToStroke(splicedPoints, cpt);
-                    const samplesLeft: Point[] = [];
-                    const samplesRight: Point[] = [];
-                    var startDir = { x: 0, y: 0 };
-                    var endDir = { x: 0, y: 0 };
-                    for (var i = 0; i < nearestSeg / 4 + 1; i++) {
-                        const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
-                        if (i === 0) startDir = bez.derivative(0);
-                        if (i === nearestSeg / 4) endDir = bez.derivative(nearestT);
-                        for (var t = 0; t < (i === nearestSeg / 4 ? nearestT + .05 : 1); t += 0.05) {
-                            const pt = bez.compute(i !== nearestSeg / 4 ? t : Math.min(nearestT, t));
-                            samplesLeft.push(new Point(pt.x, pt.y));
-                        }
+                const newink = origInk.slice();
+                const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4;
+                const splicedPoints = origInk.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8));
+                const { nearestT, nearestSeg } = InkStrokeProperties.nearestPtToStroke(splicedPoints, cpt);
+                const samplesLeft: Point[] = [];
+                const samplesRight: Point[] = [];
+                var startDir = { x: 0, y: 0 };
+                var endDir = { x: 0, y: 0 };
+                for (var i = 0; i < nearestSeg / 4 + 1; i++) {
+                    const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
+                    if (i === 0) startDir = bez.derivative(0);
+                    if (i === nearestSeg / 4) endDir = bez.derivative(nearestT);
+                    for (var t = 0; t < (i === nearestSeg / 4 ? nearestT + .05 : 1); t += 0.05) {
+                        const pt = bez.compute(i !== nearestSeg / 4 ? t : Math.min(nearestT, t));
+                        samplesLeft.push(new Point(pt.x, pt.y));
                     }
-                    var { finalCtrls } = FitOneCurve(samplesLeft, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
-                    for (var i = nearestSeg / 4; i < splicedPoints.length / 4; i++) {
-                        const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
-                        if (i === nearestSeg / 4) startDir = bez.derivative(nearestT);
-                        if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1);
-                        for (var t = i === nearestSeg / 4 ? nearestT : 0; t < (i === nearestSeg / 4 ? 1 + .05 + 1e-7 : 1 + 1e-7); t += 0.05) {
-                            const pt = bez.compute(Math.min(1, t));
-                            samplesRight.push(new Point(pt.x, pt.y));
-                        }
+                }
+                var { finalCtrls } = FitOneCurve(samplesLeft, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
+                for (var i = nearestSeg / 4; i < splicedPoints.length / 4; i++) {
+                    const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
+                    if (i === nearestSeg / 4) startDir = bez.derivative(nearestT);
+                    if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1);
+                    for (var t = i === nearestSeg / 4 ? nearestT : 0; t < (i === nearestSeg / 4 ? 1 + .05 + 1e-7 : 1 + 1e-7); t += 0.05) {
+                        const pt = bez.compute(Math.min(1, t));
+                        samplesRight.push(new Point(pt.x, pt.y));
                     }
-                    const { finalCtrls: rightCtrls, error: errorRight } = FitOneCurve(samplesRight, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
-                    finalCtrls = finalCtrls.concat(rightCtrls);
-                    newink.splice(this._currentPoint - 4, 8, ...finalCtrls);
-                    return newink;
                 }
+                const { finalCtrls: rightCtrls, error: errorRight } = FitOneCurve(samplesRight, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
+                finalCtrls = finalCtrls.concat(rightCtrls);
+                newink.splice(this._currentPoint - 4, 8, ...finalCtrls);
+                return newink;
             }
 
             return ink.map((pt, i) => {
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 06671961d..1871ab5a4 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -328,12 +328,25 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
             StrCast(this.layoutDoc.strokeOutlineColor, !closed && fillColor && fillColor !== "transparent" ? StrCast(this.layoutDoc.color, "transparent") : "transparent") :
             ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "yellow", "magenta", "cyan", "orange"][highlightIndex];
         // Invisible polygonal line that enables the ink to be selected by the user.
-        const clickableLine = (downHdlr?: (e: React.PointerEvent) => void) => InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, highlightColor,
+        const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, suppressFill: boolean = false) => InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, highlightColor,
             inkStrokeWidth, inkStrokeWidth + (highlightIndex && closed && fillColor && (new Color(fillColor)).alpha() < 1 ? 6 : 15),
             StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap),
-            StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" ? "none" : fillColor, startMarker, endMarker,
+            StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" || suppressFill ? "none" : fillColor, startMarker, endMarker,
             markerScale, undefined, inkScaleX, inkScaleY, "", this.props.pointerEvents ?? (this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted"), 0.0,
             false, downHdlr);
+        const fsize = +(StrCast(this.props.Document.fontSize, "12px").replace("px", ""));
+        const lineHeightGuess = (+getComputedStyle(document.body).lineHeight.replace("px", "")) / (+getComputedStyle(document.body).fontSize.replace("px", ""));
+        const interactions = {
+            onPointerLeave: action(() => this._nearestScrPt = undefined),
+            onPointerMove: this.props.isSelected() ? this.onPointerMove : undefined,
+            onClick: (e: React.MouseEvent) => this._handledClick && e.stopPropagation(),
+            onContextMenu: () => {
+                const cm = ContextMenu.Instance;
+                !Doc.UserDoc().noviceMode && cm?.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
+                cm?.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" });
+                cm?.addItem({ description: "Edit Points", event: action(() => InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton), icon: "paint-brush" });
+            }
+        };
         return <div className="inkStroke-wrapper">
             <svg className="inkStroke"
                 style={{
@@ -341,24 +354,19 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                     mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
                     cursor: this.props.isSelected() ? "default" : undefined
                 }}
-                onPointerLeave={action(e => this._nearestScrPt = undefined)}
-                onPointerMove={this.props.isSelected() ? this.onPointerMove : undefined}
-                onClick={e => this._handledClick && e.stopPropagation()}
-                onContextMenu={() => {
-                    const cm = ContextMenu.Instance;
-                    !Doc.UserDoc().noviceMode && cm?.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
-                    cm?.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" });
-                    cm?.addItem({ description: "Edit Points", event: action(() => InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton), icon: "paint-brush" });
-                }}
+                {...(!closed ? interactions : {})}
             >
-                {clickableLine(this.onPointerDown)}
                 {inkLine}
+                {clickableLine(this.onPointerDown)}
             </svg>
             {!closed || (!RTFCast(this.rootDoc.text)?.Text && !this.props.isSelected()) ? (null) :
                 <div className="inkStroke-text" style={{
                     color: StrCast(this.layoutDoc.textColor, "black"),
                     pointerEvents: this.props.isDocumentActive?.() ? "all" : undefined,
                     width: this.layoutDoc[WidthSym](),
+                    transform: `scale(${this.props.scaling?.() || 1})`,
+                    transformOrigin: "top left",
+                    top: (this.props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this.props.scaling?.() || 1)) / 2
                 }}>
                     <FormattedTextBox
                         {...OmitKeys(this.props, ['children']).omit}
@@ -366,7 +374,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                         yPadding={10}
                         xPadding={10}
                         fieldKey={"text"}
-                        fontSize={12}
+                        fontSize={fsize}
                         dontRegisterView={true}
                         noSidebar={true}
                         dontScale={true}
@@ -374,6 +382,16 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                     />
                 </div>
             }
+            {!closed ? null : <svg className="inkStroke"
+                style={{
+                    transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
+                    mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
+                    cursor: this.props.isSelected() ? "default" : undefined, position: "absolute"
+                }}
+                {...interactions}
+            >
+                {clickableLine(this.onPointerDown, true)}
+            </svg>}
         </div>;
     }
 }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 3f16d3c49..edadd59c0 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -38,7 +38,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     @observable _contentView: DocumentView | undefined | null;
     get displayName() { return "CollectionFreeFormDocumentView(" + this.rootDoc.title + ")"; } // this makes mobx trace() statements more descriptive
     get maskCentering() { return this.props.Document.isInkMask ? InkingStroke.MaskDim / 2 : 0; }
-    get transform() { return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${this.props.jitterRotation}deg)`; }
+    get transform() { return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`; }
     get X() { return this.dataProvider ? this.dataProvider.x : NumCast(this.Document.x); }
     get Y() { return this.dataProvider ? this.dataProvider.y : NumCast(this.Document.y); }
     get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : NumCast(this.Document.zIndex); }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index d061cdd0b..e7c43e5cd 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -369,6 +369,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         this._marqueeing = undefined;
         this.props.select(false);
     }
+    savedAnnotations = () => this._savedAnnotations;
 
     render() {
         TraceMobx();
@@ -408,7 +409,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     docView={this.props.docViewPath().slice(-1)[0]}
                     addDocument={this.addDocument}
                     finishMarquee={this.finishMarquee}
-                    savedAnnotations={this._savedAnnotations}
+                    savedAnnotations={this.savedAnnotations}
                     annotationLayer={this._annotationLayer.current}
                     mainCont={this._mainCont.current}
                     anchorMenuCrop={this.crop}
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 59e39e051..e7fc38ecd 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -537,7 +537,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     infoWidth = () => this.props.PanelWidth() / 5;
     infoHeight = () => this.props.PanelHeight() / 5;
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
-
+    savedAnnotations = () => this._savedAnnotations;
     render() {
         const renderAnnotations = (docFilters?: () => string[]) => (null);
         // bcz: commmented this out.  Otherwise, any documents that are rendered with an InfoWindow of a marker
@@ -618,7 +618,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         addDocument={this.addDocumentWrapper}
                         docView={this.props.docViewPath().lastElement()}
                         finishMarquee={this.finishMarquee}
-                        savedAnnotations={this._savedAnnotations}
+                        savedAnnotations={this.savedAnnotations}
                         annotationLayer={this._annotationLayer.current}
                         mainCont={this._mainCont.current} />}
             </div>
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index e57cb1abe..c8b72990f 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -800,6 +800,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         return <div className="videoBox-annotationLayer" style={{ transition: this.transition, height: `${this.heightPercent}%` }} ref={this._annotationLayer} />;
     }
 
+    savedAnnotations = () => this._savedAnnotations;
     render() {
         const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
         const borderRadius = borderRad?.includes("px") ? `${Number(borderRad.split("px")[0]) / this.scaling()}px` : borderRad;
@@ -847,7 +848,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         containerOffset={this.marqueeOffset}
                         addDocument={this.addDocWithTimecode}
                         finishMarquee={this.finishMarquee}
-                        savedAnnotations={this._savedAnnotations}
+                        savedAnnotations={this.savedAnnotations}
                         annotationLayer={this._annotationLayer.current}
                         mainCont={this._mainCont.current}
                     />}
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index ca13590de..d3ff2586b 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -656,7 +656,7 @@ ScriptingGlobals.add(function setFontHighlight(color?: string, checkResult?: boo
 ScriptingGlobals.add(function setFontSize(size: string | number, checkResult?: boolean) {
     const editorView = RichTextMenu.Instance?.TextView?.EditorView;
     if (checkResult) {
-        return (editorView ? RichTextMenu.Instance.fontSize : StrCast(Doc.UserDoc().fontSize, "10px")).replace("px", "");
+        return RichTextMenu.Instance.fontSize.replace("px", "");
     }
     if (typeof size === "number") size = size.toString();
     if (size && Number(size).toString() === size) size += "px";
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 4814d6e9a..c5f76cc8d 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -9,7 +9,7 @@ import { wrapInList } from "prosemirror-schema-list";
 import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import { Doc } from "../../../../fields/Doc";
-import { Cast, StrCast } from "../../../../fields/Types";
+import { Cast, NumCast, StrCast } from "../../../../fields/Types";
 import { DocServer } from "../../../DocServer";
 import { LinkManager } from "../../../util/LinkManager";
 import { SelectionManager } from "../../../util/SelectionManager";
@@ -118,7 +118,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
         this.activeListType = this.getActiveListStyle();
         this._activeAlignment = this.getActiveAlignment();
         this._activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
-        this._activeFontSize = !activeSizes.length ? "13px" : activeSizes[0];
+        this._activeFontSize = !activeSizes.length ? StrCast(this.TextView.Document.fontSize, StrCast(Doc.UserDoc().fontSize, "10px")) : activeSizes[0];
         this._activeFontColor = !activeColors.length ? "black" : activeColors.length > 0 ? String(activeColors[0]) : "...";
         this.activeHighlightColor = !activeHighlights.length ? "" : activeHighlights.length > 0 ? String(activeHighlights[0]) : "...";
 
@@ -310,10 +310,17 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
 
     setFontSize = (fontSize: string) => {
         if (this.view) {
-            const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize });
-            this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
-            this.view.focus();
-            this.updateMenu(this.view, undefined, this.props);
+            if (this.view.state.selection.from === 1 && this.view.state.selection.empty &&
+                (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) {
+                this.TextView.dataDoc.fontSize = fontSize;
+                this.view.focus();
+                this.updateMenu(this.view, undefined, this.props);
+            } else {
+                const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize });
+                this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
+                this.view.focus();
+                this.updateMenu(this.view, undefined, this.props);
+            }
         }
     }
 
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index b55b2cf44..d3876906e 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -562,6 +562,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         </>;
     }
     contentZoom = () => this._zoomed;
+    savedAnnotations = () => this._savedAnnotations;
     render() {
         TraceMobx();
         return <div className="pdfViewer-content">
@@ -586,7 +587,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                         addDocument={(doc: Doc | Doc[]) => this.props.addDocument!(doc)}
                         docView={this.props.docViewPath().lastElement()}
                         finishMarquee={this.finishMarquee}
-                        savedAnnotations={this._savedAnnotations}
+                        savedAnnotations={this.savedAnnotations}
                         annotationLayer={this._annotationLayer.current}
                         mainCont={this._mainCont.current} />}
             </div>
-- 
cgit v1.2.3-70-g09d2


From f79a109aaa2c89d1462a1299c9e4bd0a0305e115 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 11 May 2022 13:32:40 -0400
Subject: added cropped images of pdfs

---
 src/client/views/nodes/PDFBox.tsx        | 89 +++++++++++++++++++++++++++++---
 src/client/views/nodes/WebBoxRenderer.js | 26 +++++-----
 src/client/views/pdf/PDFViewer.tsx       |  8 ++-
 3 files changed, 101 insertions(+), 22 deletions(-)

(limited to 'src/client/views/pdf/PDFViewer.tsx')

diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 48465976a..98c17ed23 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -2,28 +2,30 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
-import { CreateImage } from "../nodes/WebBoxRenderer";
 import "pdfjs-dist/web/pdf_viewer.css";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
-import { Cast, NumCast, StrCast, ImageCast } from '../../../fields/Types';
+import { Id } from '../../../fields/FieldSymbols';
+import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField, PdfField } from "../../../fields/URLField";
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
-import { Docs } from '../../documents/Documents';
+import { Docs, DocUtils } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
 import { KeyCodes } from '../../util/KeyCodes';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent";
 import { Colors } from '../global/globalEnums';
+import { CreateImage } from "../nodes/WebBoxRenderer";
 import { AnchorMenu } from '../pdf/AnchorMenu';
 import { PDFViewer } from "../pdf/PDFViewer";
 import { SidebarAnnos } from '../SidebarAnnos';
 import { FieldView, FieldViewProps } from './FieldView';
+import { ImageBox } from './ImageBox';
 import "./PDFBox.scss";
-import React = require("react");
-import { Id } from '../../../fields/FieldSymbols';
 import { VideoBox } from './VideoBox';
+import React = require("react");
 
 @observer
 export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
@@ -61,10 +63,15 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement);
             }
         }
+
+        if (oldDiv.className === "pdfBox-ui" ||
+            oldDiv.className.includes("pdfViewerDash-overlay")) {
+            newDiv.style.display = "none";
+        }
         if (oldDiv instanceof HTMLCanvasElement) {
             const canvas = oldDiv;
             const img = document.createElement('img'); // create a Image Element
-            img.src = canvas.toDataURL();   //image source
+            img.src = canvas.toDataURL();   //image sourcez
             img.style.width = canvas.style.width;
             img.style.height = canvas.style.height;
             const newCan = newDiv as HTMLCanvasElement;
@@ -74,6 +81,70 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     }
 
+    crop = (region: Doc | undefined, addCrop?: boolean) => {
+        if (!region) return;
+        const cropping = Doc.MakeCopy(region, true);
+        Doc.GetProto(region).lockedPosition = true;
+        Doc.GetProto(region).title = "region:" + this.rootDoc.title;
+        Doc.GetProto(region).isPushpin = true;
+        this.addDocument(region);
+
+        const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
+        const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
+        newDiv.style.width = (this.layoutDoc[WidthSym]()).toString();
+        newDiv.style.height = (this.layoutDoc[HeightSym]()).toString();
+        this.replaceCanvases(docViewContent, newDiv);
+        const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv);
+
+        const anchx = NumCast(cropping.x);
+        const anchy = NumCast(cropping.y);
+        const anchw = cropping[WidthSym]() * (this.props.scaling?.() || 1);
+        const anchh = cropping[HeightSym]() * (this.props.scaling?.() || 1);
+        const viewScale = 1;
+        cropping.title = "crop: " + this.rootDoc.title;
+        cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
+        cropping.y = NumCast(this.rootDoc.y);
+        cropping._width = anchw;
+        cropping._height = anchh;
+        cropping.isLinkButton = undefined;
+        const croppingProto = Doc.GetProto(cropping);
+        croppingProto.annotationOn = undefined;
+        croppingProto.isPrototype = true;
+        croppingProto.proto = Cast(this.rootDoc.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
+        croppingProto.type = DocumentType.IMG;
+        croppingProto.layout = ImageBox.LayoutString("data");
+        croppingProto.data = new ImageField(Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png"));
+        croppingProto["data-nativeWidth"] = anchw;
+        croppingProto["data-nativeHeight"] = anchh;
+        if (addCrop) {
+            DocUtils.MakeLink({ doc: region }, { doc: cropping }, "cropped image", "");
+        }
+        this.props.bringToFront(cropping);
+
+        CreateImage(
+            "",
+            document.styleSheets,
+            htmlString,
+            anchw,
+            anchh,
+            NumCast(region.y) * this.props.PanelHeight() / NumCast(this.rootDoc[this.fieldKey + "-nativeHeight"]),
+            NumCast(region.x) * this.props.PanelWidth() / NumCast(this.rootDoc[this.fieldKey + "-nativeWidth"]),
+            4
+        ).then
+            ((data_url: any) => {
+                VideoBox.convertDataUri(data_url, region[Id]).then(
+                    returnedfilename => setTimeout(action(() => {
+                        croppingProto.data = new ImageField(returnedfilename);
+                    }), 500));
+            })
+            .catch(function (error: any) {
+                console.error('oops, something went wrong!', error);
+            });
+
+
+        return cropping;
+    }
+
     updateIcon = () => {
         return; // currently we render pdf icons as text labels
         const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
@@ -328,7 +399,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 transform: `scale(${scale})`,
                 position: "absolute",
                 transformOrigin: "top left",
-                top: 0
+                top: 0,
             }}>
                 <PDFViewer {...this.props}
                     rootDoc={this.rootDoc}
@@ -345,6 +416,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     removeDocument={this.removeDocument}
                     whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                     startupLive={true}
+                    crop={this.crop}
                     ContentScaling={returnOne}
                 />
             </div>
@@ -386,4 +458,5 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
         return this.renderTitleBox;
     }
-}
\ No newline at end of file
+}
+
diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js
index d2b701cf9..d9524dd6e 100644
--- a/src/client/views/nodes/WebBoxRenderer.js
+++ b/src/client/views/nodes/WebBoxRenderer.js
@@ -176,7 +176,7 @@ var ForeignHtmlRenderer = function (styleSheets) {
      * 
      * @returns {Promise<String>}
      */
-    const buildSvgDataUri = async function (webUrl, contentHtml, width, height, scroll) {
+    const buildSvgDataUri = async function (webUrl, contentHtml, width, height, scroll, xoff) {
 
         return new Promise(async function (resolve, reject) {
 
@@ -240,7 +240,7 @@ var ForeignHtmlRenderer = function (styleSheets) {
 
             // build SVG string
             const svg = `<svg xmlns='http://www.w3.org/2000/svg' width='${width}' height='${height}'>
-                        <foreignObject x='0' y='${-scroll}' width='${width}' height='${scroll + height}'>
+                        <foreignObject x='${-xoff}' y='${-scroll}' width='${xoff + width}' height='${scroll + height}'>
                             ${contentRootElemString}
                         </foreignObject>
                 </svg>`;
@@ -259,11 +259,11 @@ var ForeignHtmlRenderer = function (styleSheets) {
      * 
      * @return {Promise<Image>}
      */
-    this.renderToImage = async function (webUrl, html, width, height, scroll) {
+    this.renderToImage = async function (webUrl, html, width, height, scroll, xoff) {
         return new Promise(async function (resolve, reject) {
             const img = new Image();
             console.log("BUILDING SVG for:" + webUrl);
-            img.src = await buildSvgDataUri(webUrl, html, width, height, scroll);
+            img.src = await buildSvgDataUri(webUrl, html, width, height, scroll, xoff);
 
             img.onload = function () {
                 console.log("IMAGE SVG created:" + webUrl);
@@ -279,16 +279,16 @@ var ForeignHtmlRenderer = function (styleSheets) {
      * 
      * @return {Promise<Image>}
      */
-    this.renderToCanvas = async function (webUrl, html, width, height, scroll) {
+    this.renderToCanvas = async function (webUrl, html, width, height, scroll, xoff, oversample) {
         return new Promise(async function (resolve, reject) {
-            const img = await self.renderToImage(webUrl, html, width, height, scroll);
+            const img = await self.renderToImage(webUrl, html, width, height, scroll, xoff);
 
             const canvas = document.createElement('canvas');
-            canvas.width = img.width;
-            canvas.height = img.height;
+            canvas.width = img.width * oversample;
+            canvas.height = img.height * oversample;
 
             const canvasCtx = canvas.getContext('2d');
-            canvasCtx.drawImage(img, 0, 0, img.width, img.height);
+            canvasCtx.drawImage(img, 0, 0, img.width * oversample, img.height * oversample);
 
             resolve(canvas);
         });
@@ -301,9 +301,9 @@ var ForeignHtmlRenderer = function (styleSheets) {
      * 
      * @return {Promise<String>}
      */
-    this.renderToBase64Png = async function (webUrl, html, width, height, scroll) {
+    this.renderToBase64Png = async function (webUrl, html, width, height, scroll, xoff, oversample) {
         return new Promise(async function (resolve, reject) {
-            const canvas = await self.renderToCanvas(webUrl, html, width, height, scroll);
+            const canvas = await self.renderToCanvas(webUrl, html, width, height, scroll, xoff, oversample);
             resolve(canvas.toDataURL('image/png'));
         });
     };
@@ -311,8 +311,8 @@ var ForeignHtmlRenderer = function (styleSheets) {
 };
 
 
-export function CreateImage(webUrl, styleSheets, html, width, height, scroll) {
-    const val = (new ForeignHtmlRenderer(styleSheets)).renderToBase64Png(webUrl, html.replace(/docView-hack/g, 'documentView-hack').replace(/\n/g, "").replace(/<script((?!\/script).)*<\/script>/g, ""), width, height, scroll);
+export function CreateImage(webUrl, styleSheets, html, width, height, scroll, xoff = 0, oversample = 1) {
+    const val = (new ForeignHtmlRenderer(styleSheets)).renderToBase64Png(webUrl, html.replace(/docView-hack/g, 'documentView-hack').replace(/\n/g, "").replace(/<script((?!\/script).)*<\/script>/g, ""), width, height, scroll, xoff, oversample);
     return val;
 }
 
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index d3876906e..3c052d10b 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -48,6 +48,7 @@ interface IViewerProps extends FieldViewProps {
     setPdfViewer: (view: PDFViewer) => void;
     ContentScaling?: () => number;
     anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
+    crop: (region: Doc | undefined, addCrop?: boolean) => Doc | undefined;
 }
 
 /**
@@ -194,6 +195,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
         }
         return focusSpeed;
     }
+    crop = (region: Doc | undefined, addCrop?: boolean) => {
+        return this.props.crop(region, addCrop);
+    }
 
     @action
     setupPdfJsViewer = async () => {
@@ -589,7 +593,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
                         finishMarquee={this.finishMarquee}
                         savedAnnotations={this.savedAnnotations}
                         annotationLayer={this._annotationLayer.current}
-                        mainCont={this._mainCont.current} />}
+                        mainCont={this._mainCont.current}
+                        anchorMenuCrop={this.crop}
+                    />}
             </div>
         </div>;
     }
-- 
cgit v1.2.3-70-g09d2


From b6448779f56b3bcad108e8a2c8e75ac405524a3e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 11 May 2022 17:08:17 -0400
Subject: fixed crop of pdfs when zoomed. removed some unused pdf code. removed
 unused LOD code.

---
 src/client/documents/Documents.ts                  |   1 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   3 +-
 src/client/views/nodes/PDFBox.tsx                  |   7 +-
 src/client/views/pdf/PDFViewer.scss                |  50 ++++---
 src/client/views/pdf/PDFViewer.tsx                 | 158 ++++++++++-----------
 src/fields/documentSchemas.ts                      |   1 -
 6 files changed, 113 insertions(+), 107 deletions(-)

(limited to 'src/client/views/pdf/PDFViewer.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index dd3e13cfd..d9c2e0d8b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -116,7 +116,6 @@ export class DocumentOptions {
     _fitToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents
     _lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged
     _lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed
-    _freeformLOD?: boolean; // whether to use LOD to render a freeform document
     _isPushpin?: boolean; // whether document, when clicked, toggles display of its link target
     _showTitle?: string; // field name to display in header (:hover is an optional suffix)
     _showCaption?: string; // which field to display in the caption area.  leave empty to have no caption
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5b3a7db41..193750d8a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1558,7 +1558,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this.props.renderDepth && optionItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
         if (!Doc.UserDoc().noviceMode) {
             optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
-            optionItems.push({ description: `${this.Document._freeformLOD ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._freeformLOD = !this.Document._freeformLOD, icon: "table" });
         }
         !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
         const mores = ContextMenu.Instance.findByDescription("More...");
@@ -1739,7 +1738,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 width: `${100 / (this.contentScaling || 1)}%`,
                 height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.contentScaling || 1)}%`// : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
             }}>
-            {this._firstRender || (this.Document._freeformLOD && !this.props.isContentActive() && !this.props.isAnnotationOverlay && this.props.renderDepth > 0) ?
+            {this._firstRender ?
                 this.placeholder : this.marqueeView}
             {this.props.noOverlay ? (null) : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
 
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 98c17ed23..e6e723ef1 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -65,9 +65,10 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
 
         if (oldDiv.className === "pdfBox-ui" ||
-            oldDiv.className.includes("pdfViewerDash-overlay")) {
+            oldDiv.className === "pdfViewerDash-overlay-inking") {
             newDiv.style.display = "none";
         }
+        if (newDiv && newDiv.style) newDiv.style.overflow = "hidden";
         if (oldDiv instanceof HTMLCanvasElement) {
             const canvas = oldDiv;
             const img = document.createElement('img'); // create a Image Element
@@ -127,7 +128,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             htmlString,
             anchw,
             anchh,
-            NumCast(region.y) * this.props.PanelHeight() / NumCast(this.rootDoc[this.fieldKey + "-nativeHeight"]),
+            NumCast(region.y) * this.props.PanelWidth() / NumCast(this.rootDoc[this.fieldKey + "-nativeWidth"]),
             NumCast(region.x) * this.props.PanelWidth() / NumCast(this.rootDoc[this.fieldKey + "-nativeWidth"]),
             4
         ).then
@@ -313,7 +314,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         </>;
         const searchTitle = `${!this._searching ? "Open" : "Close"} Search Bar`;
         const curPage = NumCast(this.Document._curPage) || 1;
-        return !this.props.isContentActive() ? (null) :
+        return !this.props.isContentActive() || this._pdfViewer?.isAnnotating ? (null) :
             <div className="pdfBox-ui" onKeyDown={e => [KeyCodes.BACKSPACE, KeyCodes.DELETE].includes(e.keyCode) ? e.stopPropagation() : true}
                 onPointerDown={e => e.stopPropagation()} style={{ display: this.props.isContentActive() ? "flex" : "none" }}>
                 <div className="pdfBox-overlayCont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index 390aed1e0..822af6a68 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -1,5 +1,3 @@
- 
-
 .pdfViewer-content {
     height: 100%;
     width: 100%;
@@ -8,33 +6,42 @@
     top: 0;
     left: 0;
 }
-.pdfViewerDash, .pdfViewerDash-interactive {
+
+.pdfViewerDash,
+.pdfViewerDash-interactive {
     position: absolute;
     width: 100%;
     height: 100%;
     top: 0;
-    left:0;
+    left: 0;
     position: absolute;
     overflow-y: auto;
     overflow-x: hidden;
     transform-origin: top left;
-    
+
     // .canvasWrapper {
     //     transform: scale(0.75);
     //     transform-origin: top left;
     // }
     .textLayer {
         opacity: unset;
-        mix-blend-mode: multiply;// bcz: makes text fuzzy!
+        mix-blend-mode: multiply; // bcz: makes text fuzzy!
+
         span {
             padding-right: 5px;
             padding-bottom: 4px;
         }
     }
-    .textLayer ::selection { background: #ACCEF7; } // should match the backgroundColor in createAnnotation()
+
+    .textLayer ::selection {
+        background: #ACCEF7;
+    }
+
+    // should match the backgroundColor in createAnnotation()
     .textLayer .highlight {
         background-color: yellow;
     }
+
     .textLayer .highlight.selected {
         background-color: orange;
     }
@@ -43,32 +50,32 @@
         position: relative;
         border: unset;
     }
+
     .pdfViewerDash-text-selected {
-       // position: relative;  // bcz: this breaks auto-scrolling using the inline search box
+        // position: relative;  // bcz: this breaks auto-scrolling using the inline search box
         z-index: -1;
-        .textLayer{
-           pointer-events: all;
-           user-select: text;
+
+        .textLayer {
+            pointer-events: all;
+            user-select: text;
         }
     }
+
     .pdfViewerDash-text {
         transform-origin: top left;
+
         .textLayer {
             will-change: transform;
         }
     }
 
-    .pdfViewerDash-overlay, .pdfViewerDash-overlay-inking {
+    .pdfViewerDash-overlay {
         transform-origin: left top;
         position: absolute;
         top: 0px;
         left: 0px;
         display: inline-block;
-        width:100%;
-        pointer-events: all;
-    }
-    .pdfViewerDash-overlay {
-        pointer-events: none;
+        width: 100%;
     }
 
     .pdfViewerDash-overlayAnno {
@@ -81,7 +88,7 @@
         border-radius: 5px;
         display: block;
     }
-    
+
     .pdfViewerDash-annotationLayer {
         position: absolute;
         transform-origin: left top;
@@ -90,12 +97,13 @@
         pointer-events: none;
         mix-blend-mode: multiply; // bcz: makes text fuzzy!
     }
+
     .pdfViewerDash-waiting {
         width: 70%;
         height: 70%;
-        margin : 15%;
+        margin: 15%;
         transition: 0.4s opacity ease;
-        opacity: 0.7; 
+        opacity: 0.7;
         position: absolute;
         z-index: 10;
     }
@@ -103,4 +111,4 @@
 
 .pdfViewerDash-interactive {
     pointer-events: all;
-}   
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 3c052d10b..38890410c 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,18 +1,16 @@
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
-import { Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { Doc, DocListCast, Field, HeightSym, Opt } from "../../../fields/Doc";
 import { Id } from "../../../fields/FieldSymbols";
 import { InkTool } from "../../../fields/InkField";
-import { Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { PdfField } from "../../../fields/URLField";
 import { TraceMobx } from "../../../fields/util";
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils } from "../../../Utils";
 import { DocUtils } from "../../documents/Documents";
-import { Networking } from "../../Network";
 import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { CompiledScript, CompileScript } from "../../util/Scripting";
 import { SelectionManager } from "../../util/SelectionManager";
 import { SharingManager } from "../../util/SharingManager";
 import { SnappingManager } from "../../util/SnappingManager";
@@ -59,11 +57,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
     static _annotationStyle: any = addStyleSheet();
     @observable private _pageSizes: { width: number, height: number }[] = [];
     @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
-    @observable private _script: CompiledScript = CompileScript("return true") as CompiledScript;
     @observable private _marqueeing: number[] | undefined;
     @observable private _textSelecting = true;
     @observable private _showWaiting = true;
-    @observable private _showCover = false;
     @observable private _zoomed = 1;
     @observable private _overlayAnnoInfo: Opt<Doc>;
     @observable private Index: number = -1;
@@ -79,14 +75,13 @@ export class PDFViewer extends React.Component<IViewerProps> {
     private _selectionText: string = "";
     private _downX: number = 0;
     private _downY: number = 0;
-    private _coverPath: any;
     private _lastSearch = false;
     private _viewerIsSetup = false;
     private _ignoreScroll = false;
     private _initialScroll: Opt<number>;
     private _forcedScroll = true;
 
-
+    @observable isAnnotating = false;
     // key where data is stored
     @computed get allAnnotations() {
         return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + "-annotations"]), this.props.docFilters(), this.props.docRangeFilters(), undefined);
@@ -108,13 +103,11 @@ export class PDFViewer extends React.Component<IViewerProps> {
             if (pathComponents.length) {
                 params.subtree = `${pathComponents.join("/")}/`;
             }
-            this._coverPath = href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" };
         } else {
             const params: any = {
                 coreFilename: relative.split("/")[relative.split("/").length - 1],
                 pageNum: Math.min(this.props.pdf.numPages, Math.max(1, NumCast(this.props.Document._curPage, 1))),
             };
-            this._coverPath = "http://cs.brown.edu/~bcz/face.gif";//href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" };
         }
         runInAction(() => this._showWaiting = true);
         this.props.startupLive && this.setupPdfJsViewer();
@@ -207,17 +200,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
         this.props.setPdfViewer(this);
         await this.initialLoad();
 
-        this._disposers.filterScript = reaction(
-            () => ScriptCast(this.props.Document.filterScript),
-            action(scriptField => {
-                const oldScript = this._script.originalScript;
-                this._script = scriptField?.script.compiled ? scriptField.script : CompileScript("return true") as CompiledScript;
-                if (this._script.originalScript !== oldScript) {
-                    this.Index = -1;
-                }
-            }),
-            { fireImmediately: true });
-
         this.createPdfViewer();
     }
 
@@ -232,7 +214,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
             () => Math.abs(NumCast(this.props.Document._scrollTop)),
             (pos) => {
                 if (!this._ignoreScroll) {
-                    (this._showCover || this._showWaiting) && this.setupPdfJsViewer();
+                    this._showWaiting && this.setupPdfJsViewer();
                     const viewTrans = quickScroll ?? StrCast(this.props.Document._viewTransition);
                     const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
                     const durationSecStr = viewTrans.match(/([0-9.]*)s/);
@@ -377,6 +359,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
             this.props.select(false);
             MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
             this._marqueeing = [e.clientX, e.clientY];
+            this.isAnnotating = true;
             if (e.target && ((e.target as any).className.includes("endOfContent") || ((e.target as any).parentElement.className !== "textLayer"))) {
                 this._textSelecting = false;
                 document.addEventListener("pointermove", this.onSelectMove); // need this to prevent document from being dragged if stopPropagation doesn't get called
@@ -393,6 +376,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     @action
     finishMarquee = (x?: number, y?: number) => {
+        this.isAnnotating = false;
         this._marqueeing = undefined;
         this._textSelecting = true;
         document.removeEventListener("pointermove", this.onSelectMove);
@@ -402,6 +386,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     @action
     onSelectEnd = (e: PointerEvent): void => {
+        this.isAnnotating = false;
         clearStyleSheetRules(PDFViewer._annotationStyle);
         this.props.select(false);
         document.removeEventListener("pointermove", this.onSelectMove);
@@ -459,20 +444,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => this._setPreviewCursor = func;
 
-    getCoverImage = () => {
-        if (!this.props.Document[HeightSym]() || !Doc.NativeHeight(this.props.Document)) {
-            setTimeout((() => {
-                this.props.Document._height = this.props.Document[WidthSym]() * this._coverPath.height / this._coverPath.width;
-                Doc.SetNativeWidth(this.props.Document, (Doc.NativeWidth(this.props.Document) || 0) * this._coverPath.height / this._coverPath.width);
-            }).bind(this), 0);
-        }
-        const nativeWidth = Doc.NativeWidth(this.props.Document);
-        const nativeHeight = Doc.NativeHeight(this.props.Document);
-        const resolved = Utils.prepend(this._coverPath.path);
-        return <img key={resolved} src={resolved} onError={action(() => this._coverPath.path = "http://www.cs.brown.edu/~bcz/face.gif")} onLoad={action(() => this._showWaiting = false)}
-            style={{ position: "absolute", display: "inline-block", top: 0, left: 0, width: `${nativeWidth}px`, height: `${nativeHeight}px` }} />;
-    }
-
     @action
     onZoomWheel = (e: React.WheelEvent) => {
         if (this.props.isContentActive(true)) {
@@ -495,7 +466,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     }
 
     @computed get overlayInfo() {
-        return !this._overlayAnnoInfo || this._overlayAnnoInfo.author === Doc.CurrentUserEmail ? (null) :
+        return !this._overlayAnnoInfo ? (null) :
             <div className="pdfViewerDash-overlayAnno" style={{ top: NumCast(this._overlayAnnoInfo.y), left: NumCast(this._overlayAnnoInfo.x) }}>
                 <div className="pdfViewerDash-overlayAnno" style={{ right: -50, background: SharingManager.Instance.users.find(users => users.user.email === this._overlayAnnoInfo!.author)?.userColor }}>
                     {this._overlayAnnoInfo.author + " " + Field.toString(this._overlayAnnoInfo.creationDate as Field)}
@@ -517,41 +488,76 @@ export class PDFViewer extends React.Component<IViewerProps> {
         }
         return this.props.styleProvider?.(doc, props, property);
     }
+    renderAnnotations = (docFilters?: () => string[]) =>
+        <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
+            isAnnotationOverlay={true}
+            fieldKey={this.props.fieldKey + "-annotations"}
+            setPreviewCursor={this.setPreviewCursor}
+            PanelHeight={this.panelHeight}
+            PanelWidth={this.panelWidth}
+            dropAction={"alias"}
+            select={emptyFunction}
+            ContentScaling={this.contentZoom}
+            bringToFront={emptyFunction}
+            docFilters={docFilters || this.basicFilter}
+            styleProvider={this.childStyleProvider}
+            dontRenderDocuments={docFilters ? false : true}
+            CollectionView={undefined}
+            ScreenToLocalTransform={this.overlayTransform}
+            renderDepth={this.props.renderDepth + 1} />;
+
+    @computed get overlayLayerClickableAnnotations() {
+        return SnappingManager.GetIsDragging() ? (null) : this.renderAnnotations();
+    }
+    @computed get overlayLayerOpaqueAnnotations() {
+        trace();
+        return <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
+            isAnnotationOverlay={true}
+            fieldKey={this.props.fieldKey + "-annotations"}
+            setPreviewCursor={this.setPreviewCursor}
+            PanelHeight={this.panelHeight}
+            PanelWidth={this.panelWidth}
+            dropAction={"alias"}
+            select={emptyFunction}
+            ContentScaling={this.contentZoom}
+            bringToFront={emptyFunction}
+            docFilters={this.opaqueFilter}
+            styleProvider={this.childStyleProvider}
+            dontRenderDocuments={false}
+            CollectionView={undefined}
+            ScreenToLocalTransform={this.overlayTransform}
+            renderDepth={this.props.renderDepth + 1} />
+    }
+    @computed get overlayLayerTransparentAnnotations() {
+        trace();
+        return <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
+            isAnnotationOverlay={true}
+            fieldKey={this.props.fieldKey + "-annotations"}
+            setPreviewCursor={this.setPreviewCursor}
+            PanelHeight={this.panelHeight}
+            PanelWidth={this.panelWidth}
+            dropAction={"alias"}
+            select={emptyFunction}
+            ContentScaling={this.contentZoom}
+            bringToFront={emptyFunction}
+            docFilters={this.transparentFilter}
+            styleProvider={this.childStyleProvider}
+            dontRenderDocuments={false}
+            CollectionView={undefined}
+            ScreenToLocalTransform={this.overlayTransform}
+            renderDepth={this.props.renderDepth + 1} />
+    }
     @computed get overlayLayer() {
-        const renderAnnotations = (docFilters?: () => string[]) =>
-            <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
-                isAnnotationOverlay={true}
-                fieldKey={this.props.fieldKey + "-annotations"}
-                setPreviewCursor={this.setPreviewCursor}
-                PanelHeight={this.panelHeight}
-                PanelWidth={this.panelWidth}
-                dropAction={"alias"}
-                select={emptyFunction}
-                ContentScaling={this.contentZoom}
-                bringToFront={emptyFunction}
-                docFilters={docFilters || this.basicFilter}
-                styleProvider={this.childStyleProvider}
-                dontRenderDocuments={docFilters ? false : true}
-                CollectionView={undefined}
-                ScreenToLocalTransform={this.overlayTransform}
-                renderDepth={this.props.renderDepth + 1} />;
-        return <div>
-            <div className={`pdfViewerDash-overlay${CurrentUserUtils.SelectedTool !== InkTool.None || SnappingManager.GetIsDragging() ? "-inking" : ""}`}
-                style={{
-                    pointerEvents: SnappingManager.GetIsDragging() ? "all" : undefined,
-                    mixBlendMode: "multiply",
-                    transform: `scale(${this._zoomed})`
-                }}>
-                {renderAnnotations(this.transparentFilter)}
+        return <div className="pdfViewerDash-overlay" style={{
+            transform: `scale(${this._zoomed})`,
+            pointerEvents: CurrentUserUtils.SelectedTool !== InkTool.None || SnappingManager.GetIsDragging() ? "all" : "none",
+        }} >
+            <div className="pdfViewerDash-overlay" key="muliplied" style={{ mixBlendMode: "multiply", }}>
+                {this.overlayLayerTransparentAnnotations}
             </div>
-            <div className={`pdfViewerDash-overlay${CurrentUserUtils.SelectedTool !== InkTool.None || SnappingManager.GetIsDragging() ? "-inking" : ""}`}
-                style={{
-                    pointerEvents: SnappingManager.GetIsDragging() ? "all" : undefined,
-                    mixBlendMode: this.allAnnotations.some(anno => anno.mixBlendMode) ? "hard-light" : undefined,
-                    transform: `scale(${this._zoomed})`
-                }}>
-                {renderAnnotations(this.opaqueFilter)}
-                {SnappingManager.GetIsDragging() ? (null) : renderAnnotations()}
+            <div className="pdfViewerDash-overlay" key="blended" style={{ mixBlendMode: this.allAnnotations.some(anno => anno.mixBlendMode) ? "hard-light" : undefined, }}>
+                {this.overlayLayerOpaqueAnnotations}
+                {this.overlayLayerClickableAnnotations}
             </div>
         </div>;
     }
@@ -559,12 +565,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
         return <div className={"pdfViewerDash-text" + (this.props.pointerEvents !== "none" && this._textSelecting && this.props.isContentActive() ? "-selected" : "")} ref={this._viewer} />;
     }
     @computed get contentScaling() { return this.props.ContentScaling?.() || 1; }
-    @computed get standinViews() {
-        return <>
-            {this._showCover ? this.getCoverImage() : (null)}
-            {this._showWaiting ? <img className="pdfViewerDash-waiting" key="waiting" src={"/assets/loading.gif"} /> : (null)}
-        </>;
-    }
     contentZoom = () => this._zoomed;
     savedAnnotations = () => this._savedAnnotations;
     render() {
@@ -581,7 +581,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 {this.annotationLayer}
                 {this.overlayLayer}
                 {this.overlayInfo}
-                {this.standinViews}
+                {this._showWaiting ? <img className="pdfViewerDash-waiting" src={"/assets/loading.gif"} /> : (null)}
                 {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
                     <MarqueeAnnotator rootDoc={this.props.rootDoc}
                         getPageFromScroll={this.getPageFromScroll}
@@ -594,7 +594,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                         savedAnnotations={this.savedAnnotations}
                         annotationLayer={this._annotationLayer.current}
                         mainCont={this._mainCont.current}
-                        anchorMenuCrop={this.crop}
+                        anchorMenuCrop={this._textSelecting ? undefined : this.crop}
                     />}
             </div>
         </div>;
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 4d5ae1018..e532becb5 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -44,7 +44,6 @@ export const documentSchema = createSchema({
     _showCaption: "string",     // whether editable caption text is overlayed at the bottom of the document 
     _showTitle: "string",       // the fieldkey(s) whose contents should be displayed at the top of the document. separate multiple keys with ";".  Use :hover suffix to indicate title should be shown on hover
     _showAudio: "boolean",      // whether to show the audio record icon on documents
-    _freeformLOD: "boolean",    // whether to enable LOD switching for CollectionFreeFormViews
     _pivotField: "string",      // specifies which field key should be used as the timeline/pivot axis
     _columnsFill: "boolean",    // whether documents in a stacking view column should be sized to fill the column
     _columnsSort: "string",     // how a document should be sorted "ascending", "descending", undefined (none)   
-- 
cgit v1.2.3-70-g09d2


From 37f3c5f1d13bcb8a54d2b0a242df2ee499703dc5 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 11 May 2022 20:36:14 -0400
Subject: fixed some zorering issues with transparent/opaque docs on pdfs

---
 src/client/views/pdf/PDFViewer.tsx | 80 +++++++++++++-------------------------
 1 file changed, 27 insertions(+), 53 deletions(-)

(limited to 'src/client/views/pdf/PDFViewer.tsx')

diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 38890410c..7ad452e54 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -488,7 +488,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
         }
         return this.props.styleProvider?.(doc, props, property);
     }
-    renderAnnotations = (docFilters?: () => string[]) =>
+
+    renderAnnotations = (docFilters?: () => string[], dontRender?: boolean) =>
         <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
             isAnnotationOverlay={true}
             fieldKey={this.props.fieldKey + "-annotations"}
@@ -501,63 +502,36 @@ export class PDFViewer extends React.Component<IViewerProps> {
             bringToFront={emptyFunction}
             docFilters={docFilters || this.basicFilter}
             styleProvider={this.childStyleProvider}
-            dontRenderDocuments={docFilters ? false : true}
-            CollectionView={undefined}
-            ScreenToLocalTransform={this.overlayTransform}
-            renderDepth={this.props.renderDepth + 1} />;
-
-    @computed get overlayLayerClickableAnnotations() {
-        return SnappingManager.GetIsDragging() ? (null) : this.renderAnnotations();
-    }
-    @computed get overlayLayerOpaqueAnnotations() {
-        trace();
-        return <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
-            isAnnotationOverlay={true}
-            fieldKey={this.props.fieldKey + "-annotations"}
-            setPreviewCursor={this.setPreviewCursor}
-            PanelHeight={this.panelHeight}
-            PanelWidth={this.panelWidth}
-            dropAction={"alias"}
-            select={emptyFunction}
-            ContentScaling={this.contentZoom}
-            bringToFront={emptyFunction}
-            docFilters={this.opaqueFilter}
-            styleProvider={this.childStyleProvider}
-            dontRenderDocuments={false}
+            dontRenderDocuments={dontRender}
             CollectionView={undefined}
             ScreenToLocalTransform={this.overlayTransform}
-            renderDepth={this.props.renderDepth + 1} />
-    }
-    @computed get overlayLayerTransparentAnnotations() {
-        trace();
-        return <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
-            isAnnotationOverlay={true}
-            fieldKey={this.props.fieldKey + "-annotations"}
-            setPreviewCursor={this.setPreviewCursor}
-            PanelHeight={this.panelHeight}
-            PanelWidth={this.panelWidth}
-            dropAction={"alias"}
-            select={emptyFunction}
-            ContentScaling={this.contentZoom}
-            bringToFront={emptyFunction}
-            docFilters={this.transparentFilter}
-            styleProvider={this.childStyleProvider}
-            dontRenderDocuments={false}
-            CollectionView={undefined}
-            ScreenToLocalTransform={this.overlayTransform}
-            renderDepth={this.props.renderDepth + 1} />
+            renderDepth={this.props.renderDepth + 1}
+        />;
+    @computed get overlayTransparentAnnotations() { return this.renderAnnotations(this.transparentFilter, false); }
+    @computed get overlayOpaqueAnnotations() { return this.renderAnnotations(this.opaqueFilter, false); }
+    @computed get overlayClickableAnnotations() {
+        return <div style={{ height: NumCast(this.props.rootDoc.scrollHeight) }}>
+            {this.renderAnnotations(undefined, true)}
+        </div>;
     }
     @computed get overlayLayer() {
-        return <div className="pdfViewerDash-overlay" style={{
-            transform: `scale(${this._zoomed})`,
-            pointerEvents: CurrentUserUtils.SelectedTool !== InkTool.None || SnappingManager.GetIsDragging() ? "all" : "none",
-        }} >
-            <div className="pdfViewerDash-overlay" key="muliplied" style={{ mixBlendMode: "multiply", }}>
-                {this.overlayLayerTransparentAnnotations}
+        return <div style={{ pointerEvents: SnappingManager.GetIsDragging() ? "all" : "none" }}>
+            <div className="pdfViewerDash-overlay"
+                style={{
+                    pointerEvents: SnappingManager.GetIsDragging() ? "all" : "none",
+                    mixBlendMode: "multiply",
+                    transform: `scale(${this._zoomed})`
+                }}>
+                {this.overlayTransparentAnnotations}
             </div>
-            <div className="pdfViewerDash-overlay" key="blended" style={{ mixBlendMode: this.allAnnotations.some(anno => anno.mixBlendMode) ? "hard-light" : undefined, }}>
-                {this.overlayLayerOpaqueAnnotations}
-                {this.overlayLayerClickableAnnotations}
+            <div className="pdfViewerDash-overlay"
+                style={{
+                    pointerEvents: SnappingManager.GetIsDragging() ? "all" : "none",
+                    mixBlendMode: this.allAnnotations.some(anno => anno.mixBlendMode) ? "hard-light" : undefined,
+                    transform: `scale(${this._zoomed})`
+                }}>
+                {this.overlayOpaqueAnnotations}
+                {this.overlayClickableAnnotations}
             </div>
         </div>;
     }
-- 
cgit v1.2.3-70-g09d2


From c31bb0d5d8c4d42fb5cd97b1582de0cae1b16ca0 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 12 May 2022 12:17:12 -0400
Subject: fixed perfromance (flickering, speed) issues with having
 pointerEvents prop invalidate documents - switched to using a function to
 avoid flickering on PDFs of annotations in particular.

---
 src/Utils.ts                                       |  4 +++
 src/client/views/InkingStroke.tsx                  |  2 +-
 src/client/views/SidebarAnnos.tsx                  |  4 +--
 src/client/views/StyleProvider.tsx                 |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 14 +++++----
 src/client/views/nodes/ComparisonBox.tsx           |  4 +--
 src/client/views/nodes/DocumentView.tsx            |  4 +--
 src/client/views/nodes/FieldView.tsx               |  4 +--
 src/client/views/nodes/MapBox/MapBox.tsx           | 10 +++++--
 src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx |  4 +--
 src/client/views/nodes/PDFBox.tsx                  |  1 -
 src/client/views/nodes/WebBox.tsx                  | 12 ++++----
 src/client/views/pdf/Annotation.tsx                |  6 ++--
 src/client/views/pdf/PDFViewer.tsx                 | 34 ++++------------------
 14 files changed, 46 insertions(+), 59 deletions(-)

(limited to 'src/client/views/pdf/PDFViewer.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index 205f9379e..c3c13a555 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -440,6 +440,10 @@ export function returnTrue() { return true; }
 
 export function returnFalse() { return false; }
 
+export function returnAll() { return "all"; }
+
+export function returnNone() { return "none"; }
+
 export function returnVal(val1?: number, val2?: number) { return val1 !== undefined ? val1 : val2 !== undefined ? val2 : 0; }
 
 export function returnOne() { return 1; }
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 5e9990444..3d9c048e5 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -332,7 +332,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
             inkStrokeWidth, inkStrokeWidth + (highlightIndex && closed && fillColor && (new Color(fillColor)).alpha() < 1 ? 6 : 15),
             StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap),
             StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" || suppressFill ? "none" : fillColor, startMarker, endMarker,
-            markerScale, undefined, inkScaleX, inkScaleY, "", this.props.pointerEvents ?? (this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted"), 0.0,
+            markerScale, undefined, inkScaleX, inkScaleY, "", this.props.pointerEvents?.() ?? (this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted"), 0.0,
             false, downHdlr);
         const fsize = +(StrCast(this.props.Document.fontSize, "12px").replace("px", ""));
         // bootsrap 3 style sheet sets line height to be 20px for default 14 point font size. 
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 04c0565ea..9e939aa19 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -4,7 +4,7 @@ import { Doc, DocListCast, StrListCast, Opt } from "../../fields/Doc";
 import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
 import { NumCast, StrCast } from '../../fields/Types';
-import { emptyFunction, OmitKeys, returnOne, returnTrue, returnZero } from '../../Utils';
+import { emptyFunction, OmitKeys, returnAll, returnOne, returnTrue, returnZero } from '../../Utils';
 import { Docs, DocUtils } from '../documents/Documents';
 import { Transform } from '../util/Transform';
 import { CollectionStackingView } from './collections/CollectionStackingView';
@@ -147,7 +147,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                         renderDepth={this.props.renderDepth + 1}
                         viewType={CollectionViewType.Stacking}
                         fieldKey={this.sidebarKey}
-                        pointerEvents={"all"}
+                        pointerEvents={returnAll}
                     />
                 </div>
             </div>;
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index ca9826ab9..5056dedaf 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -204,7 +204,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         }
         case StyleProp.PointerEvents:
             if (doc?.type === DocumentType.MARKER) return "none";
-            if (props?.pointerEvents === "none") return "none";
+            if (props?.pointerEvents?.() === "none") return "none";
             const layer = doc && props?.layerProvider?.(doc);
             const isInk = doc && StrCast(Doc.Layout(doc).layout).includes(InkingStroke.name);
             if (opacity() === 0 || (isInk && !docProps?.treeViewDoc) || doc?.isInkMask) return "none";
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 193750d8a..19b6612b0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,5 +1,5 @@
 import { Bezier } from "bezier-js";
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
 import { computedFn } from "mobx-utils";
 import { DateField } from "../../../../fields/DateField";
@@ -1152,10 +1152,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     isContentActive = () => this.props.isSelected() || this.props.isContentActive();
 
+    pointerEvents = () => {
+        const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
+        const pointerEvents = this.props.isContentActive() === false ? "none" : this.backgroundActive || this.props.childPointerEvents ? "all" :
+            (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : this.props.pointerEvents?.();
+        return pointerEvents;
+    }
     getChildDocView(entry: PoolData) {
         const childLayout = entry.pair.layout;
         const childData = entry.pair.data;
-        const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
         return <CollectionFreeFormDocumentView key={childLayout[Id] + (entry.replica || "")}
             DataDoc={childData}
             Document={childLayout}
@@ -1197,8 +1202,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             bringToFront={this.bringToFront}
             showTitle={this.props.childShowTitle}
             dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
-            pointerEvents={this.props.isContentActive() === false ? "none" : this.backgroundActive || this.props.childPointerEvents ? "all" :
-                (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : this.props.pointerEvents}
+            pointerEvents={this.pointerEvents}
             jitterRotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
         //fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
         />;
@@ -1733,7 +1737,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             onContextMenu={this.onContextMenu}
             style={{
                 pointerEvents: this.props.Document.type === DocumentType.MARKER ? "none" :  // bcz: ugh.. this is here to prevent markers, which render as freeform views, from grabbing events -- need a better approach.
-                    this.backgroundEvents ? "all" : this.props.pointerEvents as any,
+                    this.backgroundEvents ? "all" : this.props.pointerEvents?.() as any,
                 transform: `scale(${this.contentScaling || 1})`,
                 width: `${100 / (this.contentScaling || 1)}%`,
                 height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.contentScaling || 1)}%`// : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 78608b681..abb01c466 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -3,7 +3,7 @@ import { action, observable } from 'mobx';
 import { observer } from "mobx-react";
 import { Doc, Opt } from '../../../fields/Doc';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, OmitKeys, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, OmitKeys, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { undoBatch } from '../../util/UndoManager';
@@ -96,7 +96,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                     Document={targetDoc}
                     DataDoc={undefined}
                     hideLinkButton={true}
-                    pointerEvents={"none"} />
+                    pointerEvents={returnNone} />
                 {clearButton(which)}
             </> :  // placeholder image if doc is missing
                 <div className="placeholder">
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 02ea87103..e117be0c2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -148,7 +148,7 @@ export interface DocumentViewSharedProps {
     ignoreAutoHeight?: boolean;
     forceAutoHeight?: boolean;
     disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
-    pointerEvents?: string;
+    pointerEvents?: () => Opt<string>;
     scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
     createNewFilterDoc?: () => void;
     updateFilterDoc?: (doc: Doc) => void;
@@ -870,7 +870,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
         return <div className="documentView-contentsView"
             style={{
-                pointerEvents: this.props.pointerEvents as any ?? this.rootDoc.layoutKey === "layout_icon" ? "none" : (this.rootDoc.type !== DocumentType.INK && ((this.props.contentPointerEvents as any) || (this.isContentActive())) ? "all" : "none"),
+                pointerEvents: this.props.pointerEvents?.() as any ?? this.rootDoc.layoutKey === "layout_icon" ? "none" : (this.rootDoc.type !== DocumentType.INK && ((this.props.contentPointerEvents as any) || (this.isContentActive())) ? "all" : "none"),
                 height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
             }}>
             {!this._retryThumb || !this.thumbShown() ? (null) :
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index ae9acfe3f..686b4308b 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -2,7 +2,7 @@ import React = require("react");
 import { computed } from "mobx";
 import { observer } from "mobx-react";
 import { DateField } from "../../../fields/DateField";
-import { Doc, Field, FieldResult } from "../../../fields/Doc";
+import { Doc, Field, FieldResult, Opt } from "../../../fields/Doc";
 import { List } from "../../../fields/List";
 import { WebField } from "../../../fields/URLField";
 import { DocumentViewSharedProps } from "./DocumentView";
@@ -25,7 +25,7 @@ export interface FieldViewProps extends DocumentViewSharedProps {
     setHeight?: (height: number) => void;
 
     // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
-    pointerEvents?: string;
+    pointerEvents?: () => Opt<string>;
     fontSize?: number;
     height?: number;
     width?: number;
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index e7fc38ecd..c0fd8d8a0 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -489,13 +489,17 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return this.addDocument(doc, annotationKey);
     }
 
+    pointerEvents = () => {
+        return this.props.isContentActive() && this.props.pointerEvents?.() !== "none" && !MarqueeOptionsMenu.Instance.isShown() ?
+            "all" :
+            SnappingManager.GetIsDragging() ?
+                undefined : "none";
+    }
     @computed get annotationLayer() {
-        const pe = this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" :
-            SnappingManager.GetIsDragging() ? undefined : "none";
         return <div className="mapBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
             {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
                 <Annotation key={`${anno[Id]}-annotation`} {...this.props}
-                    fieldKey={this.annotationKey} pointerEvents={pe} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} />)}
+                    fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} />)}
         </div>;
     }
 
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index 0d5fedb7b..db7dcb09d 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -4,7 +4,7 @@ import { observer } from "mobx-react";
 import * as React from "react";
 import { Doc } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
-import { emptyFunction, OmitKeys, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction, OmitKeys, returnAll, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { CollectionStackingView } from '../../collections/CollectionStackingView';
@@ -79,7 +79,7 @@ export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & Vi
                         addDocument={(doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.AddDocToList(this.props.place, "data", d), true as boolean)}
                         renderDepth={this.props.renderDepth + 1}
                         viewType={CollectionViewType.Stacking}
-                        pointerEvents="all"
+                        pointerEvents={returnAll}
                     />
                 </div>
                 <hr />
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index e6e723ef1..35b5e78a8 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -416,7 +416,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     moveDocument={this.moveDocument}
                     removeDocument={this.removeDocument}
                     whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
-                    startupLive={true}
                     crop={this.crop}
                     ContentScaling={returnOne}
                 />
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index f441e5d36..f01dba4d0 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -12,10 +12,9 @@ import { ComputedField } from "../../../fields/ScriptField";
 import { Cast, ImageCast, NumCast, StrCast } from "../../../fields/Types";
 import { ImageField, WebField } from "../../../fields/URLField";
 import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from "../../../Utils";
+import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
 import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { KeyCodes } from "../../util/KeyCodes";
 import { ScriptingGlobals } from "../../util/ScriptingGlobals";
 import { SnappingManager } from "../../util/SnappingManager";
 import { undoBatch } from "../../util/UndoManager";
@@ -648,7 +647,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth))
 
     @computed get content() {
-        const interactive = !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents !== "none" && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting;
+        const interactive = !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents?.() !== "none" && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting;
         return <div className={"webBox-cont" + (interactive ? "-interactive" : "")}
             onKeyDown={e => e.stopPropagation()}
             style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) || `100%` : "100%", }}>
@@ -658,10 +657,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @computed get annotationLayer() {
         TraceMobx();
-        const pe = this.pointerEvents();
         return <div className="webBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
             {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
-                <Annotation {...this.props} fieldKey={this.annotationKey} pointerEvents={pe} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)}
+                <Annotation {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)}
         </div>;
 
     }
@@ -703,9 +701,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
         return this.props.styleProvider?.(doc, props, property);
     }
-    pointerEvents = () => !this._draggingSidebar && this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none";
+    pointerEvents = () => !this._draggingSidebar && this.props.isContentActive() && this.props.pointerEvents?.() !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none";
     render() {
-        const pointerEvents = this.props.layerProvider?.(this.layoutDoc) === false ? "none" : this.props.pointerEvents ? this.props.pointerEvents as any : undefined;
+        const pointerEvents = this.props.layerProvider?.(this.layoutDoc) === false ? "none" : this.props.pointerEvents?.() as any;
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
         const scale = previewScale * (this.props.scaling?.() || 1);
         const renderAnnotations = (docFilters?: () => string[]) =>
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index b1d1d8293..218f37f3d 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -16,7 +16,7 @@ interface IAnnotationProps extends FieldViewProps {
     dataDoc: Doc;
     fieldKey: string;
     showInfo: (anno: Opt<Doc>) => void;
-    pointerEvents?: string;
+    pointerEvents?: () => Opt<string>;
 }
 @observer
 export
@@ -28,7 +28,7 @@ export
 
 interface IRegionAnnotationProps extends IAnnotationProps {
     document: Doc;
-    pointerEvents?: string;
+    pointerEvents?: () => Opt<string>;
 }
 @observer
 class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
@@ -98,7 +98,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
                 width: NumCast(this.props.document._width),
                 height: NumCast(this.props.document._height),
                 opacity: this._brushed ? 0.5 : undefined,
-                pointerEvents: this.props.pointerEvents as any,
+                pointerEvents: this.props.pointerEvents?.() as any,
                 backgroundColor: this._brushed ? "orange" : StrCast(this.props.document.backgroundColor),
             }} >
         </div>);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 7ad452e54..608ba07ff 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -8,7 +8,7 @@ import { InkTool } from "../../../fields/InkField";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { PdfField } from "../../../fields/URLField";
 import { TraceMobx } from "../../../fields/util";
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils } from "../../../Utils";
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, returnFalse, smoothScroll, Utils } from "../../../Utils";
 import { DocUtils } from "../../documents/Documents";
 import { CurrentUserUtils } from "../../util/CurrentUserUtils";
 import { SelectionManager } from "../../util/SelectionManager";
@@ -41,7 +41,6 @@ interface IViewerProps extends FieldViewProps {
     fieldKey: string;
     pdf: Pdfjs.PDFDocumentProxy;
     url: string;
-    startupLive: boolean;
     loaded?: (nw: number, nh: number, np: number) => void;
     setPdfViewer: (view: PDFViewer) => void;
     ContentScaling?: () => number;
@@ -89,28 +88,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); }
 
     componentDidMount = async () => {
-        // change the address to be the file address of the PNG version of each page
-        // file address of the pdf
-        const { url: { href } } = Cast(this.props.dataDoc[this.props.fieldKey], PdfField)!;
-        const { url: relative } = this.props;
-        if (relative.includes("/pdfs/")) {
-            const pathComponents = relative.split("/pdfs/")[1].split("/");
-            const coreFilename = pathComponents.pop()!.split(".")[0];
-            const params: any = {
-                coreFilename,
-                pageNum: Math.min(this.props.pdf.numPages, Math.max(1, NumCast(this.props.Document._curPage, 1))),
-            };
-            if (pathComponents.length) {
-                params.subtree = `${pathComponents.join("/")}/`;
-            }
-        } else {
-            const params: any = {
-                coreFilename: relative.split("/")[relative.split("/").length - 1],
-                pageNum: Math.min(this.props.pdf.numPages, Math.max(1, NumCast(this.props.Document._curPage, 1))),
-            };
-        }
         runInAction(() => this._showWaiting = true);
-        this.props.startupLive && this.setupPdfJsViewer();
+        this.setupPdfJsViewer();
         this._mainCont.current?.addEventListener("scroll", e => (e.target as any).scrollLeft = 0);
 
         this._disposers.autoHeight = reaction(() => this.props.layoutDoc._autoHeight,
@@ -456,12 +435,11 @@ export class PDFViewer extends React.Component<IViewerProps> {
         }
     }
 
-    pointerEvents = () => this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none";
+    pointerEvents = () => this.props.isContentActive() && this.props.pointerEvents?.() !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none";
     @computed get annotationLayer() {
-        const pe = this.pointerEvents();
         return <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}>
             {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
-                <Annotation {...this.props} fieldKey={this.props.fieldKey + "-annotations"} pointerEvents={pe} showInfo={this.showInfo} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)}
+                <Annotation {...this.props} fieldKey={this.props.fieldKey + "-annotations"} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)}
         </div>;
     }
 
@@ -536,7 +514,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         </div>;
     }
     @computed get pdfViewerDiv() {
-        return <div className={"pdfViewerDash-text" + (this.props.pointerEvents !== "none" && this._textSelecting && this.props.isContentActive() ? "-selected" : "")} ref={this._viewer} />;
+        return <div className={"pdfViewerDash-text" + (this.props.pointerEvents?.() !== "none" && this._textSelecting && this.props.isContentActive() ? "-selected" : "")} ref={this._viewer} />;
     }
     @computed get contentScaling() { return this.props.ContentScaling?.() || 1; }
     contentZoom = () => this._zoomed;
@@ -544,7 +522,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     render() {
         TraceMobx();
         return <div className="pdfViewer-content">
-            <div className={`pdfViewerDash${this.props.isContentActive() && this.props.pointerEvents !== "none" ? "-interactive" : ""}`} ref={this._mainCont}
+            <div className={`pdfViewerDash${this.props.isContentActive() && this.props.pointerEvents?.() !== "none" ? "-interactive" : ""}`} ref={this._mainCont}
                 onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick}
                 style={{
                     overflowX: this._zoomed !== 1 ? "scroll" : undefined,
-- 
cgit v1.2.3-70-g09d2


From e40a1e248da90af698d5ff64bd5d63d11211e6dc Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 13 May 2022 11:58:10 -0400
Subject: fixed crashing bug when quickly re-updating webbox (or other) icon. 
 fixed infinite loop in FilterBox.  changed TimeView to scale WebBox's.  fixed
 issues with timeView filters.  fixed bugs with webBox scrolling on startup,
 following links, etc.

---
 .../views/collections/CollectionTimeView.tsx       |  18 +--
 .../CollectionFreeFormLayoutEngines.tsx            |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   4 +-
 src/client/views/nodes/ComparisonBox.tsx           |   2 +-
 src/client/views/nodes/DocumentView.tsx            |  23 ++--
 src/client/views/nodes/FilterBox.tsx               |  10 +-
 src/client/views/nodes/WebBox.tsx                  | 136 +++++++++++----------
 src/client/views/pdf/Annotation.tsx                |  37 +++---
 src/client/views/pdf/PDFViewer.tsx                 |   2 +-
 src/client/views/search/SearchBox.tsx              |   4 +-
 src/fields/Doc.ts                                  |  32 ++++-
 src/server/ApiManagers/UploadManager.ts            |  14 ++-
 12 files changed, 166 insertions(+), 118 deletions(-)

(limited to 'src/client/views/pdf/PDFViewer.tsx')

diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index e63ea7151..4f6f45d2f 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -10,6 +10,7 @@ import { ComputedField, ScriptField } from "../../../fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
 import { DocumentManager } from "../../util/DocumentManager";
 import { ScriptingGlobals } from "../../util/ScriptingGlobals";
 import { ContextMenu } from "../ContextMenu";
@@ -128,6 +129,7 @@ export class CollectionTimeView extends CollectionSubView() {
         }
     }
 
+    dontScaleFilter = (doc: Doc) => doc.type === DocumentType.RTF;
     @computed get contents() {
         return <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.isContentActive() ? undefined : "none" }}
             onClick={this.contentsDown}>
@@ -137,6 +139,7 @@ export class CollectionTimeView extends CollectionSubView() {
                 childClickScript={this._childClickedScript}
                 viewDefDivClick={this._viewDefDivClick}
                 childFreezeDimensions={true}
+                dontScaleFilter={this.dontScaleFilter}
                 layoutEngine={this.layoutEngine} />
         </div>;
     }
@@ -174,7 +177,7 @@ export class CollectionTimeView extends CollectionSubView() {
             typeof (pair.layout[fieldKey]) === "string").filter(fieldKey => fieldKey[0] !== "_" && (fieldKey[0] !== "#" || fieldKey === "#") && (fieldKey === "tags" || fieldKey[0] === toUpper(fieldKey)[0])).map(fieldKey => keySet.add(fieldKey)));
         Array.from(keySet).map(fieldKey =>
             docItems.push({ description: ":" + fieldKey, event: () => this.layoutDoc._pivotField = fieldKey, icon: "compress-arrows-alt" }));
-        docItems.push({ description: ":(null)", event: () => this.layoutDoc._pivotField = undefined, icon: "compress-arrows-alt" });
+        docItems.push({ description: ":default", event: () => this.layoutDoc._pivotField = undefined, icon: "compress-arrows-alt" });
         ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" });
         const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
         ContextMenu.Instance.displayMenu(x, y, ":");
@@ -234,16 +237,17 @@ export class CollectionTimeView extends CollectionSubView() {
 }
 
 ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
+    const pivotField = StrCast(pivotDoc._pivotField) || "author";
     let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex);
     const originalFilter = StrListCast(ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField));
     pivotDoc["_prevDocFilter" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField);
     pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docRangeFilters as ObjectField);
-    pivotDoc["_prevPivotFields" + prevFilterIndex] = pivotDoc._pivotField;
+    pivotDoc["_prevPivotFields" + prevFilterIndex] = pivotField;
     pivotDoc._prevFilterIndex = ++prevFilterIndex;
-    runInAction(() => {
-        pivotDoc._docFilters = new List();
+    pivotDoc._docFilters = new List();
+    setTimeout(action(() => {
         const filterVals = (bounds.payload as string[]);
-        filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, StrCast(pivotDoc._pivotField), filterVal, "check"));
+        filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, pivotField, filterVal, "check"));
         const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc);
         if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) {
             if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) {
@@ -252,11 +256,11 @@ ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBou
         }
         const newFilters = StrListCast(pivotDoc._docFilters);
         if (newFilters.length && originalFilter.length &&
-            newFilters[newFilters.length - 1] === originalFilter[originalFilter.length - 1]) {
+            newFilters.lastElement() === originalFilter.lastElement()) {
             pivotDoc._prevFilterIndex = --prevFilterIndex;
             pivotDoc["_prevDocFilter" + prevFilterIndex] = undefined;
             pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = undefined;
             pivotDoc["_prevPivotFields" + prevFilterIndex] = undefined;
         }
-    });
+    }));
 });
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 16c7df311..9de2cfcf9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -147,7 +147,7 @@ export function computePivotLayout(
     const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
 
     let nonNumbers = 0;
-    const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField);
+    const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField) || "author";
     childPairs.map(pair => {
         const lval = pivotFieldKey === "#" || pivotFieldKey === "tags" ? Array.from(Object.keys(Doc.GetProto(pair.layout))).filter(k => k.startsWith("#")).map(k => k.substring(1)) :
             Cast(pair.layout[pivotFieldKey], listSpec("string"), null);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 19b6612b0..4f79d19ad 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -75,6 +75,7 @@ export type collectionFreeformViewProps = {
     scaleField?: string;
     noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
     engineProps?: any;
+    dontScaleFilter?: (doc: Doc) => boolean; // whether this collection should scale documents to fit their panel vs just scrolling them
     dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not.  
     // However, this screws up interactions since only the top layer gets events.  so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents.
 };
@@ -1197,10 +1198,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             layerProvider={this.props.layerProvider}
             dataProvider={this.childDataProvider}
             sizeProvider={this.childSizeProvider}
-            freezeDimensions={this.props.childFreezeDimensions}
+            freezeDimensions={BoolCast(this.props.Document.childFreezeDimensions, this.props.childFreezeDimensions)}
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             bringToFront={this.bringToFront}
             showTitle={this.props.childShowTitle}
+            dontScaleFilter={this.props.dontScaleFilter}
             dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
             pointerEvents={this.pointerEvents}
             jitterRotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index abb01c466..49cd39f43 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -87,7 +87,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
             return whichDoc ? <>
                 <DocumentView
                     ref={(r) => {
-                        whichDoc !== targetDoc && r?.focus(targetDoc);
+                        whichDoc !== targetDoc && r?.focus(whichDoc);
                     }}
                     {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
                     isContentActive={returnFalse}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e117be0c2..f30d093a1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -168,6 +168,7 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     radialMenu?: String[];
     LayoutTemplateString?: string;
     dontCenter?: "x" | "y" | "xy";
+    dontScaleFilter?: (doc: Doc) => boolean; // decides whether a document can be scaled to fit its container vs native size with scrolling
     ContentScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
     NativeWidth?: () => number;
     NativeHeight?: () => number;
@@ -249,6 +250,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             this._holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
         }
     }
+    @action
     cleanupHandlers(unbrush: boolean) {
         this._dropDisposer?.();
         this._multiTouchDisposer?.();
@@ -856,9 +858,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             this.props.isContentActive()) ? true : undefined;
     }
     @observable _retryThumb = 1;
-    thumbShown = () => !this.props.isSelected() && LightboxView.LightboxDoc !== this.rootDoc && this.thumb &&
-        !Doc.AreProtosEqual(DocumentLinksButton.StartLink, this.rootDoc) &&
-        !this._componentView?.isAnyChildContentActive?.() ? true : false
+    thumbShown = () => {
+        return !this.props.isSelected() && LightboxView.LightboxDoc !== this.rootDoc && this.thumb &&
+            !Doc.AreProtosEqual(DocumentLinksButton.StartLink, this.rootDoc) &&
+            !Doc.isBrushedHighlightedDegree(this.props.Document) &&
+            !this._componentView?.isAnyChildContentActive?.() ? true : false;
+    }
     @computed get contents() {
         TraceMobx();
         const audioView = !this.layoutDoc._showAudio ? (null) :
@@ -874,7 +879,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
             }}>
             {!this._retryThumb || !this.thumbShown() ? (null) :
-                <img style={{ background: "white", top: 0, position: "absolute" }} src={this.thumb} // + '?d=' + (new Date()).getTime()}
+                <img style={{ background: "white", top: 0, position: "relative" }} src={this.thumb} // + '?d=' + (new Date()).getTime()}
                     width={this.props.PanelWidth()} height={this.props.PanelHeight()}
                     onError={(e: any) => {
                         setTimeout(action(() => this._retryThumb = 0), 0);
@@ -1109,8 +1114,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     render() {
         TraceMobx();
         const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : 0) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
-        const highlightColor = ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "orange"][highlightIndex];
-        const highlightStyle = ["solid", "dashed", "solid", "solid"][highlightIndex];
+        const highlightColor = ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "orange", "lightBlue"][highlightIndex];
+        const highlightStyle = ["solid", "dashed", "solid", "solid", "solid"][highlightIndex];
         const excludeTypes = !this.props.treeViewDoc && highlightIndex < 3 ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON];
         let highlighting = !this.props.disableDocBrushing && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear;
         highlighting = highlighting && this.props.focus !== emptyFunction && this.layoutDoc.title !== "[pres element template]";  // bcz: hack to turn off highlighting onsidebar panel documents.  need to flag a document as not highlightable in a more direct way
@@ -1126,8 +1131,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             onKeyDown={this.onKeyDown}
             onPointerDown={this.onPointerDown}
             onClick={this.onClick}
-            onPointerEnter={e => !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document)}
-            onPointerLeave={e => !hasDescendantTarget(e.nativeEvent.x, e.nativeEvent.y, this.ContentDiv) && Doc.UnBrushDoc(this.props.Document)}
+            onPointerEnter={action(e => !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document))}
+            onPointerLeave={action(e => !hasDescendantTarget(e.nativeEvent.x, e.nativeEvent.y, this.ContentDiv) && Doc.UnBrushDoc(this.props.Document))}
             style={{
                 display: this.hidden ? "inline" : undefined,
                 borderRadius: this.borderRounding,
@@ -1207,7 +1212,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
     @computed get shouldNotScale() {
         return (this.fitWidth && !this.nativeWidth) ||
-            this.props.ContainingCollectionView?.collectionViewType === CollectionViewType.Time ||
+            this.props.dontScaleFilter?.(this.Document) ||
             this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any);
     }
     @computed get effectiveNativeWidth() { return this.shouldNotScale ? 0 : (this.nativeWidth || NumCast(this.layoutDoc.width)); }
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index dc0ab5ec2..78adf63cb 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -96,17 +96,15 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 runInAction(() => this._loaded = true);
             }, { fireImmediately: true });
     }
-    @observable _allDocs = [] as Doc[];
+
     @computed get allDocs() {
         // trace();
+        const allDocs = new Set<Doc>();
         const targetDoc = FilterBox.targetDoc;
         if (this._loaded && targetDoc) {
-            const allDocs = new Set<Doc>();
-            const activeTabs = FilterBox.targetDocChildren;
-            SearchBox.foreachRecursiveDoc(activeTabs, (depth, doc) => allDocs.add(doc));
-            setTimeout(action(() => this._allDocs = Array.from(allDocs)));
+            SearchBox.foreachRecursiveDoc(FilterBox.targetDocChildren, (depth, doc) => allDocs.add(doc));
         }
-        return this._allDocs;
+        return Array.from(allDocs);
     }
 
     @computed get _allFacets() {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 64655ba0b..af7a2b941 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -40,7 +40,6 @@ import React = require("react");
 const { CreateImage } = require("./WebBoxRenderer");
 const _global = (window /* browser */ || global /* node */) as any;
 const htmlToText = require("html-to-text");
-
 @observer
 export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
@@ -52,7 +51,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _keyInput = React.createRef<HTMLInputElement>();
-    private _initialScroll: Opt<number>;
+    private _initialScroll: Opt<number> = NumCast(this.layoutDoc.thumbScrollTop, NumCast(this.layoutDoc.scrollTop));
     private _sidebarRef = React.createRef<SidebarAnnos>();
     private _searchRef = React.createRef<HTMLInputElement>();
     private _searchString = "";
@@ -68,14 +67,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @observable private _iframeClick: HTMLIFrameElement | undefined = undefined;
     @observable private _iframe: HTMLIFrameElement | null = null;
     @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
-    @observable private _scrollHeight = NumCast(this.layoutDoc.scrollHeight, 1500);
+    @observable private _scrollHeight = NumCast(this.layoutDoc.scrollHeight);
     @computed get _url() { return this.webField?.toString() || ""; }
     @computed get _urlHash() { return this._url ? WebBox.urlHash(this._url) + "" : ""; }
-    @computed get scrollHeight() { return this._scrollHeight; }
+    @computed get scrollHeight() { return Math.max(this.layoutDoc[HeightSym](), this._scrollHeight); }
     @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); }
     @computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); }
     @computed get webField() { return Cast(this.dataDoc[this.props.fieldKey], WebField)?.url; }
-    @computed get webThumb() { return ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url; }
+    @computed get webThumb() { return this.props.thumbShown?.() && ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumbScrollTop === this.layoutDoc._scrollTop ? this.layoutDoc.thumb : undefined))?.url }
 
     constructor(props: any) {
         super(props);
@@ -100,14 +99,49 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
         return true;
     }
+    @action
     setScrollPos = (pos: number) => {
         if (!this._outerRef.current || this._outerRef.current.scrollHeight < pos) {
-            setTimeout(() => this.setScrollPos(pos), 250);
+            if (this._webPageHasBeenRendered) setTimeout(() => this.setScrollPos(pos), 250);
         } else {
             this._outerRef.current.scrollTop = pos;
             this._initialScroll = undefined;
         }
     }
+
+    lockout = false;
+    updateThumb = async () => {
+        const imageBitmap = ImageCast(this.layoutDoc["thumb-frozen"])?.url.href;
+        const scrollTop = NumCast(this.layoutDoc._scrollTop);
+        if (!this.lockout && this._iframe && !imageBitmap && scrollTop !== this.layoutDoc.thumbScrollTop) {
+            var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument);
+            if (!htmlString) {
+                htmlString = await (await fetch(Utils.CorsProxy(this.webField!.href))).text();
+            }
+            this.layoutDoc.thumb = undefined;
+            this.lockout = true; // lock to prevent multiple thumb updates.
+            const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
+            CreateImage(
+                this._webUrl.endsWith("/") ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl,
+                this._iframe.contentDocument?.styleSheets ?? [],
+                htmlString,
+                nativeWidth,
+                nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(),
+                scrollTop
+            ).then
+                ((data_url: any) => {
+                    VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true, this.layoutDoc[Id] + "-icon").then(
+                        returnedfilename => setTimeout(action(() => {
+                            this.lockout = false;
+                            this.layoutDoc.thumb = new ImageField(returnedfilename);
+                            this.layoutDoc.thumbScrollTop = scrollTop
+                        }), 500));
+                })
+                .catch(function (error: any) {
+                    console.error('oops, something went wrong!', error);
+                });
+        }
+    }
     async componentDidMount() {
         this.props.setContentView?.(this); // this tells the DocumentView that this WebBox is the "content" of the document.  this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons)
 
@@ -117,45 +151,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             this.dataDoc[this.fieldKey + "-annotations"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`);
             this.dataDoc[this.fieldKey + "-sidebar"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`);
         });
-        reaction(() => this.props.isSelected(),
+        reaction(() => this.props.isSelected() || this.isAnyChildContentActive() || Doc.isBrushedHighlightedDegree(this.props.Document),
             async (selected) => {
                 if (selected) {
                     this._webPageHasBeenRendered = true;
-                    setTimeout(action(() => {
-                        this._scrollHeight = Math.max(this.scrollHeight, this._iframe?.contentDocument?.body.scrollHeight || 0);
-                        if (this._initialScroll !== undefined) {
-                            this.setScrollPos(this._initialScroll);
-                        }
-                    }));
                 } else if ((!this.props.isContentActive() || SnappingManager.GetIsDragging()) &&   // update thumnail when unselected AND (no child annotation is active OR we've started dragging the document in which case no additional deselect will occur so this is the only chance to update the thumbnail)
                     !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && // don't create a thumbnail when double-clicking to enter lightbox because thumbnail will be empty
                     LightboxView.LightboxDoc !== this.rootDoc) { // don't create a thumbnail if entering Lightbox from maximize either, since thumb will be empty.
-                    const imageBitmap = ImageCast(this.layoutDoc["thumb-frozen"])?.url.href;
-                    if (this._iframe && !imageBitmap) {
-                        var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument);
-                        if (!htmlString) {
-                            htmlString = await (await fetch(Utils.CorsProxy(this.webField!.href))).text();
-                        }
-                        this.layoutDoc.thumb = undefined;
-                        const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
-                        CreateImage(
-                            this._webUrl.endsWith("/") ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl,
-                            this._iframe.contentDocument?.styleSheets ?? [],
-                            htmlString,
-                            nativeWidth,
-                            nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(),
-                            NumCast(this.layoutDoc._scrollTop)
-                        ).then
-                            ((data_url: any) => {
-                                VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true, this.layoutDoc[Id] + "-icon").then(
-                                    returnedfilename => setTimeout(action(() => this.layoutDoc.thumb = new ImageField(returnedfilename)), 500));
-                            })
-                            .catch(function (error: any) {
-                                console.error('oops, something went wrong!', error);
-                            });
-                    }
+                    this.updateThumb();
                 }
-            }, { fireImmediately: this.props.isSelected() });
+            }, { fireImmediately: this.props.isSelected() || this.isAnyChildContentActive() || (Doc.isBrushedHighlightedDegree(this.props.Document) ? true : false) });
 
         this._disposers.autoHeight = reaction(() => this.layoutDoc._autoHeight,
             autoHeight => {
@@ -183,21 +188,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             }
         }
 
-        var quickScroll = true;
         this._disposers.scrollReaction = reaction(() => NumCast(this.layoutDoc._scrollTop),
             (scrollTop) => {
-                if (quickScroll) this._initialScroll = scrollTop;
-                else {
-                    const viewTrans = StrCast(this.Document._viewTransition);
-                    const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
-                    const durationSecStr = viewTrans.match(/([0-9.]*)s/);
-                    const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
-                    this.goTo(scrollTop, duration);
-                }
+                const viewTrans = StrCast(this.Document._viewTransition);
+                const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
+                const durationSecStr = viewTrans.match(/([0-9.]*)s/);
+                const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
+                this.goTo(scrollTop, duration);
             },
             { fireImmediately: true }
         );
-        quickScroll = false;
     }
     @action componentWillUnmount() {
         Object.values(this._disposers).forEach(disposer => disposer?.());
@@ -234,7 +234,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     menuControls = () => this.urlEditor; // controls to be added to the top bar when a document of this type is selected
 
     scrollFocus = (doc: Doc, smooth: boolean) => {
-        this.props.select(false); // hack to preven the webBox from being replaced with a thumbnail that currently doesn't show the the anchors
         if (StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl), !smooth);
         if (DocListCast(this.props.Document[this.fieldKey + "-sidebar"]).includes(doc) && !this.SidebarShown) {
             this.toggleSidebar(!smooth);
@@ -242,15 +241,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
         if (doc !== this.rootDoc && this._outerRef.current) {
             const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
-            const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * .1, this.getScrollHeight());
-            if (scrollTo !== undefined) {
+            const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * .1,
+                Math.max(NumCast(doc.y) + doc[HeightSym](), this.getScrollHeight()));
+            if (scrollTo !== undefined && this._initialScroll === undefined) {
                 const focusSpeed = smooth ? 500 : 0;
-                this._initialScroll !== undefined && (this._initialScroll = scrollTo);
                 this.goTo(scrollTo, focusSpeed);
                 return focusSpeed;
+            } else if (!this._webPageHasBeenRendered || !this.getScrollHeight() || this._initialScroll !== undefined) {
+                this._initialScroll = scrollTo;
             }
         }
-        this._initialScroll = NumCast(this.layoutDoc._scrollTop);
         return undefined;
     }
 
@@ -324,6 +324,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @action
     iframeLoaded = (e: any) => {
         const iframe = this._iframe;
+        if (this._initialScroll !== undefined) {
+            this.setScrollPos(this._initialScroll);
+        }
         let requrlraw = decodeURIComponent(iframe?.contentWindow?.location.href.replace(Utils.prepend("") + "/corsProxy/", "") ?? this._url.toString());
         if (requrlraw !== this._url.toString()) {
             if (requrlraw.match(/q=.*&/)?.length && this._url.toString().match(/q=.*&/)?.length) {
@@ -342,8 +345,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         if (iframe?.contentDocument) {
             iframe.contentDocument.addEventListener("pointerup", this.iframeUp);
             iframe.contentDocument.addEventListener("pointerdown", this.iframeDown);
-            this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument.body.scrollHeight);
-            setTimeout(action(() => this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0)), 5000);
+            this._scrollHeight = Math.max(this._scrollHeight, iframe?.contentDocument.body.scrollHeight);
+            setTimeout(action(() => this._scrollHeight = Math.max(this._scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0)), 5000);
             iframe.setAttribute("enable-annotation", "true");
             iframe.contentDocument.addEventListener("click", undoBatch(action((e: MouseEvent) => {
                 let href = "";
@@ -374,29 +377,29 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     setDashScrollTop = (scrollTop: number, timeout: number = 250) => {
-        const iframeHeight = Math.max(1000, this._scrollHeight - this.panelHeight());
-        timeout = scrollTop > iframeHeight ? 0 : timeout;
+        const iframeHeight = Math.max(scrollTop, this._scrollHeight - this.panelHeight());
         this._scrollTimer && clearTimeout(this._scrollTimer);
         this._scrollTimer = setTimeout(action(() => {
             this._scrollTimer = undefined;
             if (!LinkDocPreview.LinkInfo && this._outerRef.current &&
                 (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) {
-                this.layoutDoc._scrollTop = this._outerRef.current.scrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop;
+                this.layoutDoc.thumb = undefined;
+                this.layoutDoc.thumbScrollTop = undefined;
+                this.layoutDoc.scrollTop = this._outerRef.current.scrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop;
             }
         }), timeout);
     }
 
     goTo = (scrollTop: number, duration: number) => {
         if (this._outerRef.current) {
-            const iframeHeight = Math.max(1000, this._scrollHeight - this.panelHeight());
-            scrollTop = scrollTop > iframeHeight + 50 ? iframeHeight : scrollTop;
+            const iframeHeight = Math.max(scrollTop, this._scrollHeight - this.panelHeight());
             if (duration) {
                 smoothScroll(duration, [this._outerRef.current], scrollTop);
                 this.setDashScrollTop(scrollTop, duration);
             } else {
                 this.setDashScrollTop(scrollTop);
             }
-        }
+        } else this._initialScroll = scrollTop;
     }
 
     forward = (checkAvailable?: boolean) => {
@@ -456,6 +459,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             if (url && !preview) {
                 this.dataDoc[this.fieldKey + "-history"] = new List<string>([...(history || []), url]);
                 this.layoutDoc._scrollTop = 0;
+                if (this._webPageHasBeenRendered) {
+                    this.layoutDoc.thumb = undefined;
+                    this.layoutDoc.thumbScrollTop = undefined;
+                }
                 future && (future.length = 0);
             }
             if (!preview) {
@@ -566,6 +573,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @computed get urlContent() {
         if (this._hackHide || (this.webThumb && (!this._webPageHasBeenRendered && LightboxView.LightboxDoc !== this.rootDoc))) return (null);
+        this.props.thumbShown?.();
         const field = this.dataDoc[this.props.fieldKey];
         let view;
         if (field instanceof HtmlField) {
@@ -583,7 +591,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 style={{ pointerEvents: this._scrollTimer ? "none" : undefined }} // if we allow pointer events when scrolling is on, then reversing direction does not work smoothly
                 ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={"https://crossorigin.me/https://cs.brown.edu"} />;
         }
-        setTimeout(action(() => this._webPageHasBeenRendered = true));
+        setTimeout(action(() => {
+            this._scrollHeight = Math.max(this._scrollHeight, this._iframe && this._iframe.contentDocument && this._iframe.contentDocument.body ? this._iframe.contentDocument.body.scrollHeight : 0);
+            if (this._initialScroll === undefined && !this._webPageHasBeenRendered) this.setScrollPos(NumCast(this.layoutDoc.thumbScrollTop, NumCast(this.layoutDoc.scrollTop)));
+            this._webPageHasBeenRendered = true;
+        }));
         return view;
     }
 
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 218f37f3d..fb2e33e2a 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -22,7 +22,11 @@ interface IAnnotationProps extends FieldViewProps {
 export
     class Annotation extends React.Component<IAnnotationProps> {
     render() {
-        return DocListCast(this.props.anno.textInlineAnnotations).map(a => <RegionAnnotation pointerEvents={this.props.pointerEvents} {...this.props} document={a} key={a[Id]} />);
+        return <div>
+            {DocListCast(this.props.anno.textInlineAnnotations).map(a =>
+                <RegionAnnotation pointerEvents={this.props.pointerEvents} {...this.props} document={a} key={a[Id]} />
+            )}
+        </div>
     }
 }
 
@@ -32,23 +36,10 @@ interface IRegionAnnotationProps extends IAnnotationProps {
 }
 @observer
 class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
-    private _disposers: { [name: string]: IReactionDisposer } = {};
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
 
-    @observable _brushed: boolean = false;
     @computed get annoTextRegion() { return Cast(this.props.document.annoTextRegion, Doc, null) || this.props.document; }
 
-    componentDidMount() {
-        this._disposers.brush = reaction(
-            () => this.annoTextRegion && Doc.isBrushedHighlightedDegree(this.annoTextRegion),
-            brushed => brushed !== undefined && runInAction(() => this._brushed = brushed !== 0)
-        );
-    }
-
-    componentWillUnmount() {
-        Object.values(this._disposers).forEach(disposer => disposer?.());
-    }
-
     @undoBatch
     deleteAnnotation = () => {
         const docAnnotations = DocListCast(this.props.dataDoc[this.props.fieldKey]);
@@ -91,15 +82,27 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
     }
 
     render() {
-        return (<div className="htmlAnnotation" onPointerEnter={() => this.props.showInfo(this.props.anno)} onPointerLeave={() => this.props.showInfo(undefined)} onPointerDown={this.onPointerDown} ref={this._mainCont}
+        const brushed = this.annoTextRegion && Doc.isBrushedHighlightedDegree(this.annoTextRegion);
+        return (<div className="htmlAnnotation" ref={this._mainCont}
+            onPointerEnter={action(() => {
+                Doc.BrushDoc(this.props.anno);
+                this.props.showInfo(this.props.anno);
+            })}
+            onPointerLeave={action(() => {
+                Doc.UnBrushDoc(this.props.anno);
+                this.props.showInfo(undefined);
+            })}
+            onPointerDown={this.onPointerDown}
             style={{
                 left: NumCast(this.props.document.x),
                 top: NumCast(this.props.document.y),
                 width: NumCast(this.props.document._width),
                 height: NumCast(this.props.document._height),
-                opacity: this._brushed ? 0.5 : undefined,
+                opacity: brushed === Doc.DocBrushStatus.highlighted ? 0.5 : undefined,
                 pointerEvents: this.props.pointerEvents?.() as any,
-                backgroundColor: this._brushed ? "orange" : StrCast(this.props.document.backgroundColor),
+                outline: brushed === Doc.DocBrushStatus.linkHighlighted ? "solid 1px lightBlue" : undefined,
+                backgroundColor: brushed === Doc.DocBrushStatus.highlighted ? "orange" :
+                    StrCast(this.props.document.backgroundColor),
             }} >
         </div>);
     }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 608ba07ff..b0a5fc93b 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -484,7 +484,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
             CollectionView={undefined}
             ScreenToLocalTransform={this.overlayTransform}
             renderDepth={this.props.renderDepth + 1}
-        />;
+        />
     @computed get overlayTransparentAnnotations() { return this.renderAnnotations(this.transparentFilter, false); }
     @computed get overlayOpaqueAnnotations() { return this.renderAnnotations(this.opaqueFilter, false); }
     @computed get overlayClickableAnnotations() {
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 3d36efbc4..2905e96d9 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -127,9 +127,11 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
     static foreachRecursiveDoc(docs: Doc[], func: (depth: number, doc: Doc) => void) {
         let newarray: Doc[] = [];
         var depth = 0;
+        let visited: Doc[] = [];
         while (docs.length > 0) {
             newarray = [];
-            docs.filter(d => d).forEach(d => {
+            docs.filter(d => d && !visited.includes(d)).forEach(d => {
+                visited.push(d);
                 const fieldKey = Doc.LayoutFieldKey(d);
                 const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView");
                 const data = d[annos ? fieldKey + "-annotations" : fieldKey];
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 50e5fcbc4..9de8d0831 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1016,12 +1016,28 @@ export namespace Doc {
         unbrushed = 0,
         protoBrushed = 1,
         selfBrushed = 2,
-        highlighted = 3
+        highlighted = 3,
+        linkHighlighted = 4,
     }
     // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message)
     export function IsBrushedDegreeUnmemoized(doc: Doc) {
         if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return DocBrushStatus.unbrushed;
-        return brushManager.BrushedDoc.has(doc) ? DocBrushStatus.selfBrushed : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed;
+        const status = brushManager.BrushedDoc.has(doc) ? DocBrushStatus.selfBrushed : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed;
+        if (status === DocBrushStatus.unbrushed) {
+            const lastBrushed = Array.from(brushManager.BrushedDoc.keys()).lastElement();
+            if (lastBrushed) {
+                for (var link of LinkManager.Instance.getAllDirectLinks(lastBrushed)) {
+                    const a1 = Cast(link.anchor1, Doc, null);
+                    const a2 = Cast(link.anchor2, Doc, null);
+                    if (Doc.AreProtosEqual(a1, doc) || Doc.AreProtosEqual(a2, doc) ||
+                        (Doc.AreProtosEqual(Cast(a1.annotationOn, Doc, null), doc)) ||
+                        (Doc.AreProtosEqual(Cast(a2.annotationOn, Doc, null), doc))) {
+                        return DocBrushStatus.linkHighlighted;
+                    }
+                }
+            }
+        }
+        return status;
     }
     export function IsBrushedDegree(doc: Doc) {
         return computedFn(function IsBrushDegree(doc: Doc) {
@@ -1030,14 +1046,18 @@ export namespace Doc {
     }
     export function BrushDoc(doc: Doc) {
         if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
-        brushManager.BrushedDoc.set(doc, true);
-        brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
+        runInAction(() => {
+            brushManager.BrushedDoc.set(doc, true);
+            brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
+        });
         return doc;
     }
     export function UnBrushDoc(doc: Doc) {
         if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
-        brushManager.BrushedDoc.delete(doc);
-        brushManager.BrushedDoc.delete(Doc.GetProto(doc));
+        runInAction(() => {
+            brushManager.BrushedDoc.delete(doc);
+            brushManager.BrushedDoc.delete(Doc.GetProto(doc));
+        });
         return doc;
     }
 
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 83a2bc79b..cd85a7a65 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -271,10 +271,10 @@ export default class UploadManager extends ApiManager {
                 return imageDataUri.outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, origSuffix))).then((savedName: string) => {
                     const ext = extname(savedName).toLowerCase();
                     const { pngs, jpgs } = AcceptableMedia;
-                    const resizers = !origSuffix ? [{ resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: "_m" }] : [
-                        { resizer: sharp().resize(100, undefined, { withoutEnlargement: true }), suffix: "_s" },
-                        { resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: "_m" },
-                        { resizer: sharp().resize(900, undefined, { withoutEnlargement: true }), suffix: "_l" },
+                    const resizers = !origSuffix ? [{ resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Medium }] : [
+                        { resizer: sharp().resize(100, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Small },
+                        { resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Medium },
+                        { resizer: sharp().resize(900, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Large },
                     ];
                     let isImage = false;
                     if (pngs.includes(ext)) {
@@ -290,8 +290,10 @@ export default class UploadManager extends ApiManager {
                     }
                     if (isImage) {
                         resizers.forEach(resizer => {
-                            const path = serverPathToFile(Directory.images, filename + resizer.suffix + ext);
-                            createReadStream(savedName).pipe(resizer.resizer).pipe(createWriteStream(path));
+                            const path = serverPathToFile(Directory.images, InjectSize(filename, resizer.suffix) + ext);
+                            createReadStream(savedName).on("error", e => console.log("Resizing Read error:" + e))
+                                .pipe(resizer.resizer)
+                                .pipe(createWriteStream(path).on("error", e => console.log("Resizing write error: " + e)));
                         });
 
                     }
-- 
cgit v1.2.3-70-g09d2


From e6b882080471d915a80400e7e9a97ff78a232147 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 18 May 2022 14:05:39 -0400
Subject: fixed linking bug so that both start and end link can be regions. 
 added zoom to target checkbox for link editing.  extracted out a new field
 pointerEvents so that lockPosition is not automatically conflratd with no
 pointer events.  updated freeformview focus to work with zooming on region
 selections.

---
 src/client/documents/Documents.ts                  |  3 +-
 src/client/views/DocumentButtonBar.tsx             |  2 +-
 src/client/views/PropertiesView.tsx                | 37 +++++++++++++++++++---
 src/client/views/StyleProvider.tsx                 |  5 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 24 +++++++-------
 src/client/views/linking/LinkEditor.scss           | 18 +++++++++++
 src/client/views/linking/LinkEditor.tsx            | 30 +++++++++++++++---
 src/client/views/nodes/DocumentView.tsx            |  4 +--
 src/client/views/nodes/ImageBox.tsx                |  6 ++--
 src/client/views/nodes/PDFBox.tsx                  |  2 +-
 src/client/views/nodes/WebBox.tsx                  |  4 ++-
 src/client/views/pdf/PDFViewer.tsx                 | 29 +++++++++--------
 12 files changed, 120 insertions(+), 44 deletions(-)

(limited to 'src/client/views/pdf/PDFViewer.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 797a129c4..2dd8de089 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -173,6 +173,7 @@ export class DocumentOptions {
     label?: string;
     hidden?: boolean;
     _hidden?: boolean;
+    pointerEvents?: string; // pointer events that the documentview should have
     mediaState?: string; // status of media document: "pendingRecording", "recording", "paused", "playing"
     autoPlayAnchors?: boolean; // whether to play audio/video when an anchor is clicked in a stackedTimeline.
     dontPlayLinkOnSelect?: boolean;  // whether an audio/video should start playing when a link is followed to it.
@@ -482,7 +483,7 @@ export namespace Docs {
             }],
             [DocumentType.MARKER, {
                 layout: { view: CollectionView, dataField: defaultDataKey },
-                options: { links: "@links(self)", hideLinkButton: true }
+                options: { links: "@links(self)", hideLinkButton: true, pointerEvents: "none" }
             }],
             [DocumentType.INK, { // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method
                 layout: { view: InkingStroke, dataField: defaultDataKey },
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 9b95f1525..ffa168f6b 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -192,7 +192,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
             <div className="dash-tooltip">{"Set onClick to follow primary link"}</div>}>
             <div className="documentButtonBar-icon"
                 style={{ backgroundColor: targetDoc.isLinkButton ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: targetDoc.isLinkButton ? Colors.BLACK : Colors.WHITE }}
-                onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, false, false)))}>
+                onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, undefined, false)))}>
                 <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="hand-point-right" />
             </div>
         </Tooltip>;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index bba2ac211..bcfd2dd56 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1227,6 +1227,13 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc.displayArrow = !this.selectedDoc.displayArrow)));
     }
 
+    toggleZoomToTarget1 = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => Cast(this.selectedDoc.anchor1, Doc, null).followLinkZoom = !Cast(this.selectedDoc.anchor1, Doc, null).followLinkZoom)));
+    }
+    toggleZoomToTarget2 = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => Cast(this.selectedDoc.anchor2, Doc, null).followLinkZoom = !Cast(this.selectedDoc.anchor2, Doc, null).followLinkZoom)));
+    }
+
     @computed
     get editRelationship() {
         return <input
@@ -1283,18 +1290,18 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                     </div>
                     <div className="propertiesView-section">
                         <p className="propertiesView-label">Information</p>
-                        <div className="propertiesView-input first" id="propertiesView-category">
+                        <div className="propertiesView-input first">
                             <p>Link Relationship</p>
                             {this.editRelationship}
                         </div>
-                        <div className="propertiesView-input" id="propertiesView-description">
+                        <div className="propertiesView-input">
                             <p>Description</p>
                             {this.editDescription}
                         </div>
                     </div>
                     <div className="propertiesView-section">
                         <p className="propertiesView-label">Behavior</p>
-                        <div className="propertiesView-input inline first" id="propertiesView-follow">
+                        <div className="propertiesView-input inline first">
                             <p>Follow</p>
                             <select
                                 name="selectList"
@@ -1314,7 +1321,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                                     : null}
                             </select>
                         </div>
-                        <div className="propertiesView-input inline" id="propertiesView-anchor">
+                        <div className="propertiesView-input inline">
                             <p>Auto-move anchor</p>
                             <button
                                 style={{ background: this.selectedDoc.hidden ? "gray" : !this.selectedDoc.linkAutoMove ? "" : "#4476f7", borderRadius: 3 }}
@@ -1324,7 +1331,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                                 <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
                             </button>
                         </div>
-                        <div className="propertiesView-input inline" id="propertiesView-displayArrow">
+                        <div className="propertiesView-input inline">
                             <p>Display arrow</p>
                             <button
                                 style={{ background: this.selectedDoc.hidden ? "gray" : !this.selectedDoc.displayArrow ? "" : "#4476f7", borderRadius: 3 }}
@@ -1334,6 +1341,26 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                                 <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
                             </button>
                         </div>
+                        <div className="propertiesView-input inline">
+                            <p>Zoom to target</p>
+                            <button
+                                style={{ background: this.selectedDoc.hidden ? "gray" : !Cast(this.selectedDoc.anchor1, Doc, null).followLinkZoom ? "" : "#4476f7", borderRadius: 3 }}
+                                onPointerDown={this.toggleZoomToTarget1} onClick={e => e.stopPropagation()}
+                                className="propertiesButton"
+                            >
+                                <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
+                            </button>
+                        </div>
+                        <div className="propertiesView-input inline">
+                            <p>Zoom to source</p>
+                            <button
+                                style={{ background: this.selectedDoc.hidden ? "gray" : !Cast(this.selectedDoc.anchor2, Doc, null).followLinkZoom ? "" : "#4476f7", borderRadius: 3 }}
+                                onPointerDown={this.toggleZoomToTarget2} onClick={e => e.stopPropagation()}
+                                className="propertiesButton"
+                            >
+                                <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
+                            </button>
+                        </div>
                     </div>
                 </div >;
             }
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 334415b38..93da2fa19 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -55,6 +55,7 @@ function darkScheme() { return CurrentUserUtils.ActiveDashboard?.colorScheme ===
 function toggleLockedPosition(doc: Doc) {
     UndoManager.RunInBatch(() => runInAction(() => {
         doc._lockedPosition = !doc._lockedPosition;
+        doc._pointerEvents = doc._lockedPosition ? "none" : undefined;
     }), "toggleBackground");
 }
 
@@ -194,10 +195,10 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             }
         }
         case StyleProp.PointerEvents:
-            if (doc?.type === DocumentType.MARKER) return "none";
+            if (doc?.pointerEvents) return StrCast(doc.pointerEvents);
             if (props?.pointerEvents?.() === "none") return "none";
             const isInk = doc && StrCast(Doc.Layout(doc).layout).includes(InkingStroke.name);
-            if (doc?._lockedPosition || opacity() === 0 || (isInk && !docProps?.treeViewDoc) || doc?.isInkMask) return "none";
+            if (opacity() === 0 || (isInk && !docProps?.treeViewDoc) || doc?.isInkMask) return "none";
             if (!isInk) return "all";
             return undefined;
         case StyleProp.Decorations:
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b37754b73..916cc9c05 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1081,7 +1081,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             const xfToCollection = options?.docTransform ?? Transform.Identity();
             const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: this.Document[this.scaleFieldKey] };
             const newState = HistoryUtil.getState();
-            const cantTransform = this.props.isAnnotationOverlay || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
+            const cantTransform = /*this.props.isAnnotationOverlay ||*/ ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
             const { panX, panY, scale } = cantTransform ? savedState : this.calculatePanIntoView(doc, xfToCollection, options?.willZoom ? options?.scale || .75 : undefined);
             if (!cantTransform) {   // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
                 newState.initializers![this.Document[Id]] = { panX: panX, panY: panY };
@@ -1092,8 +1092,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             const focusSpeed = options?.instant ? 0 : didMove ? (doc.focusSpeed !== undefined ? Number(doc.focusSpeed) : 500) : 0;
             // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
             if (didMove) {
-                this.setPan(panX, panY, focusSpeed, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
                 scale && (this.Document[this.scaleFieldKey] = scale);
+                this.setPan(panX, panY, focusSpeed, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
             }
 
             const startTime = Date.now();
@@ -1130,23 +1130,24 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => {
         const layoutdoc = Doc.Layout(doc);
-        const pw = this.props.PanelWidth() / NumCast(this.layoutDoc._viewScale, 1);
-        const ph = this.props.PanelHeight() / NumCast(this.layoutDoc._viewScale, 1);
         const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y));
         const pt2 = xf.transformPoint(NumCast(doc.x) + layoutdoc[WidthSym](), NumCast(doc.y) + layoutdoc[HeightSym]());
-        const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1] };
-        const cx = NumCast(this.layoutDoc._panX);
-        const cy = NumCast(this.layoutDoc._panY);
-        const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 };
+        const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1], width: pt2[0] - pt[0], height: pt2[1] - pt[1] };
 
         if (scale) {
             const maxZoom = 5; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context
+            const newScale = Math.min(maxZoom, 1 / (this.contentScaling || 1) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
             return {
-                panX: (bounds.left + bounds.right) / 2,
-                panY: (bounds.top + bounds.bot) / 2,
-                scale: Math.min(maxZoom, scale * Math.min(this.props.PanelWidth() / Math.abs(pt2[0] - pt[0]), this.props.PanelHeight() / Math.abs(pt2[1] - pt[1])))
+                panX: this.props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2,
+                panY: this.props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2,
+                scale: newScale
             };
         }
+        const pw = this.props.PanelWidth() / NumCast(this.layoutDoc._viewScale, 1);
+        const ph = this.props.PanelHeight() / NumCast(this.layoutDoc._viewScale, 1);
+        const cx = NumCast(this.layoutDoc._panX);
+        const cy = NumCast(this.layoutDoc._panY);
+        const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 };
         if ((screen.right - screen.left) < (bounds.right - bounds.left) ||
             (screen.bot - screen.top) < (bounds.bot - bounds.top)) {
             return {
@@ -1990,6 +1991,7 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor
 }
 
 export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) {
+    SelectionManager.DeselectAll();
     dv.props.focus(dv.props.Document, {
         willZoom: true, afterFocus: async (didMove) => {
             if (!didMove) {
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
index abd413f57..1d6496d3c 100644
--- a/src/client/views/linking/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
@@ -60,6 +60,24 @@
     }
 }
 
+.linkEditor-zoomFollow {
+    padding-left: 26px;
+    padding-right: 6.5px;
+    padding-bottom: 3.5px;
+    display: flex;
+
+    .linkEditor-zoomFollow-label {
+        text-decoration-color: black;
+        color: black;
+        line-height: 1.7;
+    }
+
+    .linkEditor-zoomFollow-input {
+        display: block;
+        width: 20px;
+    }
+}
+
 .linkEditor-description {
     padding-left: 26px;
     padding-right: 6.5px;
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index db331bb75..c3e0aff11 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -9,7 +9,6 @@ import { undoBatch } from "../../util/UndoManager";
 import './LinkEditor.scss';
 import { LinkRelationshipSearch } from "./LinkRelationshipSearch";
 import React = require("react");
-import { ToString } from "../../../fields/FieldSymbols";
 
 
 interface LinkEditorProps {
@@ -23,6 +22,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
 
     @observable description = Field.toString(LinkManager.currentLink?.description as any as Field);
     @observable relationship = StrCast(LinkManager.currentLink?.linkRelationship);
+    @observable zoomFollow1 = StrCast(this.props.sourceDoc.followLinkZoom);
     @observable openDropdown: boolean = false;
     @observable showInfo: boolean = false;
     @computed get infoIcon() { if (this.showInfo) { return "chevron-up"; } return "chevron-down"; }
@@ -143,9 +143,9 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
     @action
     handleDescriptionChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.description = e.target.value; }
     @action
-    handleRelationshipChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-        this.relationship = e.target.value;
-    }
+    handleRelationshipChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.relationship = e.target.value; }
+    @action
+    handleZoomFollowChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.props.sourceDoc.followLinkZoom = e.target.checked; }
     @action
     handleRelationshipSearchChange = (result: string) => {
         this.setRelationshipValue(result);
@@ -183,6 +183,27 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
             </div>
         </div>;
     }
+    @computed
+    get editZoomFollow() {
+        //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS
+        return <div className="linkEditor-zoomFollow">
+            <div className="linkEditor-zoomFollow-label">Zoom To Link Target:</div>
+            <div className="linkEditor-zoomFollow-input">
+                <div className="linkEditor-zoomFollow-editing">
+                    <input
+                        style={{ width: "100%" }}
+                        type="checkbox"
+                        value={this.zoomFollow1}
+                        autoComplete={"off"}
+                        placeholder={""}
+                        onChange={this.handleZoomFollowChange}
+                        onFocus={this.onFocus}
+                        onBlur={this.onBlur}
+                    ></input>
+                </div>
+            </div>
+        </div>;
+    }
 
     @computed
     get editDescription() {
@@ -303,6 +324,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
 
                 {this.editDescription}
                 {this.editRelationship}
+                {this.editZoomFollow}
                 {this.followingDropdown}
             </div>
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b6a2fae1a..49c2761b2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -605,7 +605,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
 
     @undoBatch @action
-    toggleFollowLink = (location: Opt<string>, zoom: boolean, setPushpin: boolean): void => {
+    toggleFollowLink = (location: Opt<string>, zoom?: boolean, setPushpin?: boolean): void => {
         this.Document.ignoreClick = false;
         if (setPushpin) {
             this.Document.isPushpin = !this.Document.isPushpin;
@@ -614,7 +614,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             this.Document._isLinkButton = !this.Document._isLinkButton;
         }
         if (this.Document._isLinkButton && !this.onClickHandler) {
-            this.Document.followLinkZoom = zoom;
+            zoom !== undefined && (this.Document.followLinkZoom = zoom);
             this.Document.followLinkLocation = location;
         } else if (this.Document._isLinkButton && this.onClickHandler) {
             this.Document._isLinkButton = false;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 3b04aa807..dd0acb311 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,7 +1,7 @@
 import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
 import { observer } from "mobx-react";
 import { extname } from 'path';
-import { DataSym, Doc, DocListCast, WidthSym } from '../../../fields/Doc';
+import { DataSym, Doc, DocListCast, Opt, WidthSym } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -49,6 +49,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _disposers: { [name: string]: IReactionDisposer } = {};
+    private _getAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
     @observable _curSuffix = "";
     @observable _uploadIcon = uploadIcons.idle;
 
@@ -62,7 +63,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
 
     getAnchor = () => {
-        const anchor = AnchorMenu.Instance?.GetAnchor(this._savedAnnotations);
+        const anchor = this._getAnchor?.(this._savedAnnotations);
         anchor && this.addDocument(anchor);
         return anchor ?? this.rootDoc;
     }
@@ -366,6 +367,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     }
     @action
     finishMarquee = () => {
+        this._getAnchor = AnchorMenu.Instance?.GetAnchor;
         this._marqueeing = undefined;
         this.props.select(false);
     }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 35b5e78a8..cbe7a5cc6 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -198,7 +198,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }
     getAnchor = () => {
         const anchor =
-            AnchorMenu.Instance?.GetAnchor() ??
+            this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations()) ??
             Docs.Create.TextanchorDocument({
                 title: StrCast(this.rootDoc.title + "@" + NumCast(this.layoutDoc._scrollTop)?.toFixed(0)),
                 y: NumCast(this.layoutDoc._scrollTop),
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 445df8ddd..2112c1d44 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -52,6 +52,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _keyInput = React.createRef<HTMLInputElement>();
     private _initialScroll: Opt<number> = NumCast(this.layoutDoc.thumbScrollTop, NumCast(this.layoutDoc.scrollTop));
+    private _getAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
     private _sidebarRef = React.createRef<SidebarAnnos>();
     private _searchRef = React.createRef<HTMLInputElement>();
     private _searchString = "";
@@ -256,7 +257,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     getAnchor = () => {
         const anchor =
-            AnchorMenu.Instance?.GetAnchor(this._savedAnnotations) ??
+            this._getAnchor(this._savedAnnotations) ??
             Docs.Create.WebanchorDocument(this._url, {
                 title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop),
                 y: NumCast(this.layoutDoc._scrollTop),
@@ -555,6 +556,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     }
     @action finishMarquee = (x?: number, y?: number, e?: PointerEvent) => {
+        this._getAnchor = AnchorMenu.Instance?.GetAnchor;
         this._marqueeing = undefined;
         this._isAnnotating = false;
         this._iframeClick = undefined;
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index b0a5fc93b..305b1fe68 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -6,7 +6,6 @@ import { Doc, DocListCast, Field, HeightSym, Opt } from "../../../fields/Doc";
 import { Id } from "../../../fields/FieldSymbols";
 import { InkTool } from "../../../fields/InkField";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { PdfField } from "../../../fields/URLField";
 import { TraceMobx } from "../../../fields/util";
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, returnFalse, smoothScroll, Utils } from "../../../Utils";
 import { DocUtils } from "../../documents/Documents";
@@ -59,7 +58,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @observable private _marqueeing: number[] | undefined;
     @observable private _textSelecting = true;
     @observable private _showWaiting = true;
-    @observable private _zoomed = 1;
     @observable private _overlayAnnoInfo: Opt<Doc>;
     @observable private Index: number = -1;
 
@@ -70,6 +68,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
+    public _getAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
     _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     private _selectionText: string = "";
     private _downX: number = 0;
@@ -184,7 +183,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     pagesinit = () => {
         if (this._pdfViewer._setDocumentViewerElement?.offsetParent) {
-            runInAction(() => this._pdfViewer.currentScaleValue = this._zoomed = 1);
+            runInAction(() => this._pdfViewer.currentScaleValue = this.props.layoutDoc._viewScale = 1);
             this.gotoPage(NumCast(this.props.Document._curPage, 1));
         }
         document.removeEventListener("pagesinit", this.pagesinit);
@@ -355,6 +354,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     @action
     finishMarquee = (x?: number, y?: number) => {
+        this._getAnchor = AnchorMenu.Instance?.GetAnchor;
         this.isAnnotating = false;
         this._marqueeing = undefined;
         this._textSelecting = true;
@@ -387,13 +387,14 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 const rect = clientRects.item(i);
                 if (rect && rect.width !== this._mainCont.current.clientWidth && rect.width) {
                     const scaleX = this._mainCont.current.offsetWidth / boundingRect.width;
+                    const pdfScale = NumCast(this.props.layoutDoc._viewScale, 1);
                     const annoBox = document.createElement("div");
                     annoBox.className = "marqueeAnnotator-annotationBox";
                     // transforms the positions from screen onto the pdf div
-                    annoBox.style.top = ((rect.top - boundingRect.top) * scaleX / this._zoomed + this._mainCont.current.scrollTop).toString();
-                    annoBox.style.left = ((rect.left - boundingRect.left) * scaleX / this._zoomed).toString();
-                    annoBox.style.width = (rect.width * this._mainCont.current.offsetWidth / boundingRect.width / this._zoomed).toString();
-                    annoBox.style.height = (rect.height * this._mainCont.current.offsetHeight / boundingRect.height / this._zoomed).toString();
+                    annoBox.style.top = ((rect.top - boundingRect.top) * scaleX / pdfScale + this._mainCont.current.scrollTop).toString();
+                    annoBox.style.left = ((rect.left - boundingRect.left) * scaleX / pdfScale).toString();
+                    annoBox.style.width = (rect.width * this._mainCont.current.offsetWidth / boundingRect.width / pdfScale).toString();
+                    annoBox.style.height = (rect.height * this._mainCont.current.offsetHeight / boundingRect.height / pdfScale).toString();
                     this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, this.getPageFromScroll(rect.top));
                 }
             }
@@ -430,14 +431,14 @@ export class PDFViewer extends React.Component<IViewerProps> {
             if (e.ctrlKey) {
                 const curScale = Number(this._pdfViewer.currentScaleValue);
                 this._pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale - curScale * e.deltaY / 1000));
-                this._zoomed = Number(this._pdfViewer.currentScaleValue);
+                this.props.layoutDoc._viewScale = Number(this._pdfViewer.currentScaleValue);
             }
         }
     }
 
     pointerEvents = () => this.props.isContentActive() && this.props.pointerEvents?.() !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none";
     @computed get annotationLayer() {
-        return <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}>
+        return <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})` }} ref={this._annotationLayer}>
             {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
                 <Annotation {...this.props} fieldKey={this.props.fieldKey + "-annotations"} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)}
         </div>;
@@ -453,7 +454,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     }
 
     showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno);
-    overlayTransform = () => this.scrollXf().scale(1 / this._zoomed);
+    overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._viewScale, 1));
     panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
     panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
     basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter("textInlineAnnotations")];
@@ -498,7 +499,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 style={{
                     pointerEvents: SnappingManager.GetIsDragging() ? "all" : "none",
                     mixBlendMode: "multiply",
-                    transform: `scale(${this._zoomed})`
+                    transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})`
                 }}>
                 {this.overlayTransparentAnnotations}
             </div>
@@ -506,7 +507,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 style={{
                     pointerEvents: SnappingManager.GetIsDragging() ? "all" : "none",
                     mixBlendMode: this.allAnnotations.some(anno => anno.mixBlendMode) ? "hard-light" : undefined,
-                    transform: `scale(${this._zoomed})`
+                    transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})`
                 }}>
                 {this.overlayOpaqueAnnotations}
                 {this.overlayClickableAnnotations}
@@ -517,7 +518,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         return <div className={"pdfViewerDash-text" + (this.props.pointerEvents?.() !== "none" && this._textSelecting && this.props.isContentActive() ? "-selected" : "")} ref={this._viewer} />;
     }
     @computed get contentScaling() { return this.props.ContentScaling?.() || 1; }
-    contentZoom = () => this._zoomed;
+    contentZoom = () => NumCast(this.props.layoutDoc._viewScale, 1);
     savedAnnotations = () => this._savedAnnotations;
     render() {
         TraceMobx();
@@ -525,7 +526,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
             <div className={`pdfViewerDash${this.props.isContentActive() && this.props.pointerEvents?.() !== "none" ? "-interactive" : ""}`} ref={this._mainCont}
                 onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick}
                 style={{
-                    overflowX: this._zoomed !== 1 ? "scroll" : undefined,
+                    overflowX: NumCast(this.props.layoutDoc._viewScale, 1) !== 1 ? "scroll" : undefined,
                     height: !this.props.Document._fitWidth && (window.screen.width > 600) ? Doc.NativeHeight(this.props.Document) : `${100 / this.contentScaling}%`,
                     transform: `scale(${this.contentScaling})`
                 }}  >
-- 
cgit v1.2.3-70-g09d2


From 8799738abd11a878579814e64163e0f8a95b5116 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 3 Jun 2022 08:10:42 -0400
Subject: fixed pushpin link toggling when zoom is not set.  fixed adding
 annotations to webBox's  without opening sidebar.  Made linkDoc preview
 always open

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx  | 2 +-
 src/client/views/nodes/DocumentView.tsx                              | 5 +++--
 src/client/views/nodes/LinkDocPreview.tsx                            | 2 +-
 src/client/views/nodes/WebBox.tsx                                    | 2 +-
 src/client/views/pdf/PDFViewer.tsx                                   | 2 +-
 5 files changed, 7 insertions(+), 6 deletions(-)

(limited to 'src/client/views/pdf/PDFViewer.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 0ba2ae1a2..09bb1526e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1077,7 +1077,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             this.props.focus(doc, options);
         } else {
             const xfToCollection = options?.docTransform ?? Transform.Identity();
-            const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: this.Document[this.scaleFieldKey] };
+            const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willZoom ? this.Document[this.scaleFieldKey] : undefined };
             const newState = HistoryUtil.getState();
             const cantTransform = /*this.props.isAnnotationOverlay ||*/ ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
             const { panX, panY, scale } = cantTransform ? savedState : this.calculatePanIntoView(doc, xfToCollection, options?.willZoom ? options?.scale || .75 : undefined);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 040ac7ae5..9db4cbde3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -480,6 +480,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
             let stopPropagate = true;
             let preventDefault = true;
+            const isScriptBox = () => StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name);
             (this.rootDoc._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc);
             if (this._doubleTap && (this.props.Document.type !== DocumentType.FONTICON || this.onDoubleClickHandler)) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
                 if (this._timeout) {
@@ -505,7 +506,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     SelectionManager.DeselectAll();
                     Doc.UnBrushDoc(this.props.Document);
                 }
-            } else if (this.onClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name)) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
+            } else if (this.onClickHandler?.script && !isScriptBox()) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
                 const { clientX, clientY, shiftKey } = e;
                 const func = () => this.onClickHandler.script.run({
                     this: this.layoutDoc,
@@ -521,7 +522,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     runInAction(() => this._pendingDoubleClick = true);
                     this._timeout = setTimeout(() => { this._timeout = undefined; clickFunc(); }, 350);
                 } else clickFunc();
-            } else if (this.allLinks && this.Document.type !== DocumentType.LINK && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
+            } else if (this.allLinks && this.Document.type !== DocumentType.LINK && !isScriptBox() && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
                 this.allLinks.length && LinkManager.FollowLink(undefined, this.props.Document, this.props, e.altKey);
             } else {
                 if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {  // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index ba515fb89..b6a9cd49b 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -93,7 +93,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                         } else {
                             this._linkSrc = anchor;
                             const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
-                            this._targetDoc = linkTarget?.type === DocumentType.MARKER && linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
+                            this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
                         }
                         this._toolTipText = "";
                     }
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index dbbcdc9de..fea8ed0a4 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -756,7 +756,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                 removeDocument={this.removeDocument}
                 moveDocument={this.moveDocument}
-                addDocument={this.sidebarAddDocument}
+                addDocument={this.addDocument}
                 styleProvider={this.childStyleProvider}
                 childPointerEvents={this.props.isContentActive() ? "all" : undefined}
                 pointerEvents={this.annotationPointerEvents} />;
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 305b1fe68..6db36f36a 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -154,7 +154,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         if (doc !== this.props.rootDoc && mainCont) {
             const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
             const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, .1 * windowHeight, NumCast(this.props.Document.scrollHeight));
-            if (scrollTo !== undefined) {
+            if (scrollTo !== undefined && scrollTo != this.props.layoutDoc._scrollTop) {
                 focusSpeed = 500;
 
                 if (!this._pdfViewer) this._initialScroll = scrollTo;
-- 
cgit v1.2.3-70-g09d2


From ba7824a91da113cc813c58b678719092c96d79a2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 3 Jun 2022 16:02:24 -0400
Subject: fixed opening closed tabs from Files sidebar to re-use best Alias
 before making a new alias.

---
 src/.DS_Store                                      | Bin 8196 -> 8196 bytes
 src/client/util/CurrentUserUtils.ts                |   2 +-
 src/client/util/DocumentManager.ts                 |  28 ++++++++++-----------
 src/client/views/LightboxView.tsx                  |   5 ++--
 src/client/views/collections/TabDocView.tsx        |  15 ++++++++++-
 src/client/views/collections/TreeView.tsx          |   4 +--
 src/client/views/nodes/DocumentIcon.tsx            |   4 +--
 src/client/views/nodes/DocumentView.tsx            |   4 ---
 src/client/views/nodes/button/FontIconBox.tsx      |  11 ++++----
 .../views/nodes/formattedText/DashFieldView.tsx    |   6 ++---
 .../views/nodes/formattedText/FormattedTextBox.tsx |   2 +-
 src/client/views/nodes/trails/PresBox.tsx          |  14 ++---------
 src/client/views/pdf/PDFViewer.tsx                 |   2 +-
 13 files changed, 48 insertions(+), 49 deletions(-)

(limited to 'src/client/views/pdf/PDFViewer.tsx')

diff --git a/src/.DS_Store b/src/.DS_Store
index b0987293b..4bf9cdac7 100644
Binary files a/src/.DS_Store and b/src/.DS_Store differ
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 2ed6c3cbe..85ad58726 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -869,7 +869,7 @@ export class CurrentUserUtils {
                             Doc.AddDocToList(menuBtnDoc, "data", newSubMenuBtnDoc, after, false, !prevSub);
                         }
                         prevSub = params.title;
-                    })
+                    });
                 }
                 prev = params.title;
             });
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index aeddc3249..a82f99d5a 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -16,7 +16,7 @@ import { SelectionManager } from './SelectionManager';
 export class DocumentManager {
 
     //global holds all of the nodes (regardless of which collection they're in)
-    @observable public DocumentViews: DocumentView[] = [];
+    @observable public DocumentViews = new Set<DocumentView>();
     @observable public LinkAnchorBoxViews: DocumentView[] = [];
     @observable public RecordingEvent = 0;
     @observable public LinkedDocumentViews: { a: DocumentView, b: DocumentView, l: Doc }[] = [];
@@ -46,7 +46,7 @@ export class DocumentManager {
             // this.LinkedDocumentViews.forEach(view => console.log("  LV = " + view.a.props.Document.title + "/" + view.a.props.LayoutTemplateString + " --> " +
             //     view.b.props.Document.title + "/" + view.b.props.LayoutTemplateString));
         } else {
-            this.DocumentViews.push(view);
+            this.DocumentViews.add(view);
         }
     }
     public RemoveView = action((view: DocumentView) => {
@@ -61,8 +61,7 @@ export class DocumentManager {
             const index = this.LinkAnchorBoxViews.indexOf(view);
             this.LinkAnchorBoxViews.splice(index, 1);
         } else {
-            const index = this.DocumentViews.indexOf(view);
-            index !== -1 && this.DocumentViews.splice(index, 1);
+            this.DocumentViews.delete(view);
         }
         SelectionManager.DeselectView(view);
     });
@@ -70,13 +69,13 @@ export class DocumentManager {
     //gets all views
     public getDocumentViewsById(id: string) {
         const toReturn: DocumentView[] = [];
-        DocumentManager.Instance.DocumentViews.map(view => {
+        Array.from(DocumentManager.Instance.DocumentViews).map(view => {
             if (view.rootDoc[Id] === id) {
                 toReturn.push(view);
             }
         });
         if (toReturn.length === 0) {
-            DocumentManager.Instance.DocumentViews.map(view => {
+            Array.from(DocumentManager.Instance.DocumentViews).map(view => {
                 const doc = view.rootDoc.proto;
                 if (doc && doc[Id] && doc[Id] === id) {
                     toReturn.push(view);
@@ -96,14 +95,14 @@ export class DocumentManager {
         const passes = preferredCollection ? [preferredCollection, undefined] : [undefined];
 
         for (const pass of passes) {
-            DocumentManager.Instance.DocumentViews.map(view => {
+            Array.from(DocumentManager.Instance.DocumentViews).map(view => {
                 if (view.rootDoc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) {
                     toReturn = view;
                     return;
                 }
             });
             if (!toReturn) {
-                DocumentManager.Instance.DocumentViews.map(view => {
+                Array.from(DocumentManager.Instance.DocumentViews).map(view => {
                     const doc = view.rootDoc.proto;
                     if (doc && doc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) {
                         toReturn = view;
@@ -122,9 +121,8 @@ export class DocumentManager {
     }
 
     public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
-        const docViews = DocumentManager.Instance.DocumentViews;
         const views: DocumentView[] = [];
-        docViews.map(view => LightboxView.IsLightboxDocView(view.docViewPath) && Doc.AreProtosEqual(view.rootDoc, toFind) && views.push(view));
+        Array.from(DocumentManager.Instance.DocumentViews).map(view => LightboxView.IsLightboxDocView(view.docViewPath) && Doc.AreProtosEqual(view.rootDoc, toFind) && views.push(view));
         return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined);
     }
     public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
@@ -133,8 +131,8 @@ export class DocumentManager {
     }
     public getDocumentViews(toFind: Doc): DocumentView[] {
         const toReturn: DocumentView[] = [];
-        const docViews = DocumentManager.Instance.DocumentViews.filter(view => !LightboxView.IsLightboxDocView(view.docViewPath));
-        const lightViews = DocumentManager.Instance.DocumentViews.filter(view => LightboxView.IsLightboxDocView(view.docViewPath));
+        const docViews = Array.from(DocumentManager.Instance.DocumentViews).filter(view => !LightboxView.IsLightboxDocView(view.docViewPath));
+        const lightViews = Array.from(DocumentManager.Instance.DocumentViews).filter(view => LightboxView.IsLightboxDocView(view.docViewPath));
 
         // heuristic to return the "best" documents first:
         //   choose a document in the lightbox first
@@ -290,7 +288,7 @@ export class DocumentManager {
         }
     }
 }
-export function DocFocusOrOpen(doc: any, collectionDoc?: Doc) {
+export function DocFocusOrOpen(doc: Doc, collectionDoc?: Doc) {
     const cv = collectionDoc && DocumentManager.Instance.getDocumentView(collectionDoc);
     const dv = DocumentManager.Instance.getDocumentView(doc, (cv?.ComponentView as CollectionFreeFormView)?.props.CollectionView);
     if (dv && Doc.AreProtosEqual(dv.props.Document, doc)) {
@@ -300,7 +298,9 @@ export function DocFocusOrOpen(doc: any, collectionDoc?: Doc) {
     else {
         const context = doc.context !== Doc.UserDoc().myFilesystem && Cast(doc.context, Doc, null);
         const showDoc = context || doc;
-        CollectionDockingView.AddSplit(showDoc === Doc.GetProto(showDoc) ? Doc.MakeAlias(showDoc) : showDoc, "right") && context &&
+        const bestAlias = showDoc === Doc.GetProto(showDoc) ? DocListCast(showDoc.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail) : showDoc;
+
+        CollectionDockingView.AddSplit(bestAlias ? bestAlias : Doc.MakeAlias(showDoc), "right") && context &&
             setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc));
     }
 }
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index ddfc8b249..3d2769f66 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -275,9 +275,8 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                 <div className="lightboxView-tabBtn" title={"open in tab"}
                     onClick={e => {
                         e.stopPropagation();
-                        // CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, "");
-                        LightboxView.openInTabFunc(LightboxView._docTarget || LightboxView._doc!, "inPlace");
-                        console.log("lightbox to tab triggered")
+                        CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, "");
+                        //LightboxView.openInTabFunc(LightboxView._docTarget || LightboxView._doc!, "inPlace");
                         SelectionManager.DeselectAll();
                         LightboxView.SetLightboxDoc(undefined);
                     }}>
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 8d84e9a56..d64cb2fd7 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -66,6 +66,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     get stack() { return (this.props as any).glContainer.parent.parent; }
     get tab() { return (this.props as any).glContainer.tab; }
     get view() { return this._view; }
+    _lastTab: any;
+    _lastView: DocumentView | undefined;
 
     @action
     init = (tab: any, doc: Opt<Doc>) => {
@@ -266,6 +268,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
 
     componentWillUnmount() {
         this._tabReaction?.();
+        this._view && DocumentManager.Instance.RemoveView(this._view);
         this.tab && CollectionDockingView.Instance.tabMap.delete(this.tab);
 
         this.props.glContainer.layoutManager.off("activeContentItemChanged", this.onActiveContentItemChanged);
@@ -353,7 +356,11 @@ export class TabDocView extends React.Component<TabDocViewProps> {
 
     @computed get docView() {
         return !this._activated || !this._document || this._document._viewType === CollectionViewType.Docking ? (null) :
-            <><DocumentView key={this._document[Id]} ref={action((r: DocumentView) => this._view = r)}
+            <><DocumentView key={this._document[Id]} ref={action((r: DocumentView) => {
+                this._lastView && DocumentManager.Instance.RemoveView(this._lastView);
+                this._view = r;
+                this._lastView = this._view;
+            })}
                 renderDepth={0}
                 Document={this._document}
                 DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined}
@@ -409,6 +416,12 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 height: "100%", width: "100%"
             }} ref={ref => {
                 if (this._mainCont = ref) {
+                    if (this._lastTab) {
+                        console.log("DUP tab")
+                        this._view && DocumentManager.Instance.RemoveView(this._view);
+                        CollectionDockingView.Instance.tabMap.delete(this._lastTab);
+                    }
+                    this._lastTab = this.tab;
                     (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document);
                     DocServer.GetRefField(this.props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document)));
                     new _global.ResizeObserver(action((entries: any) => this._forceInvalidateScreenToLocal++)).observe(ref);
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 661db7997..9de44fb00 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -183,7 +183,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             this.treeViewOpen = !this.treeViewOpen;
         } else {
             // choose an appropriate alias or make one. --- choose the first alias that (1) user owns,  (2) has no context field ... otherwise make a new alias
-            const bestAlias = docView.props.Document.author === Doc.CurrentUserEmail ? docView.props.Document : DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
+            const bestAlias = docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsPrototype(docView.props.Document) ? docView.props.Document : DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
             const nextBestAlias = DocListCast(this.props.document.aliases).find(doc => doc.author === Doc.CurrentUserEmail);
             this.props.addDocTab(bestAlias ?? nextBestAlias ?? Doc.MakeAlias(this.props.document), "lightbox");
         }
@@ -609,7 +609,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     // TODO: currently doc focus works, but can't seem to edit title
     // onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick));
     onChildClick = () => {
-        return this.props.onChildClick?.() ?? (ScriptField.MakeFunction(`DocFocusOrOpen(self)`)! || this._editTitleScript?.())
+        return this.props.onChildClick?.() ?? (ScriptField.MakeFunction(`DocFocusOrOpen(self)`)! || this._editTitleScript?.());
     }
 
     onChildDoubleClick = () => (!this.props.treeView.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick);
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index a9c998757..56de2d1fc 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -52,7 +52,7 @@ export class DocumentIconContainer extends React.Component {
                 };
             },
             getVars() {
-                const docs = DocumentManager.Instance.DocumentViews;
+                const docs = Array.from(DocumentManager.Instance.DocumentViews);
                 const capturedVariables: { [name: string]: Field } = {};
                 usedDocuments.forEach(index => capturedVariables[`d${index}`] = docs[index].props.Document);
                 return { capturedVariables };
@@ -60,6 +60,6 @@ export class DocumentIconContainer extends React.Component {
         };
     }
     render() {
-        return DocumentManager.Instance.DocumentViews.map((dv, i) => <DocumentIcon key={i} index={i} view={dv} />);
+        return Array.from(DocumentManager.Instance.DocumentViews).map((dv, i) => <DocumentIcon key={i} index={i} view={dv} />);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 14ededaf8..6e4d6a52e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -242,7 +242,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @computed get onPointerDownHandler() { return this.props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown); }
     @computed get onPointerUpHandler() { return this.props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp); }
 
-
     componentWillUnmount() { this.cleanupHandlers(true); }
     componentDidMount() { this.setupHandlers(); }
     //componentDidUpdate() { this.setupHandlers(); }
@@ -1126,9 +1125,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     render() {
         TraceMobx();
         const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : Doc.DocBrushStatus.unbrushed) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
-        if (highlightIndex === 6) {
-            console.log("")
-        }
         const highlightColor = ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "orange", "lightBlue"][highlightIndex];
         const highlightStyle = ["solid", "dashed", "solid", "solid", "solid"][highlightIndex];
         const excludeTypes = !this.props.treeViewDoc ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON];
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index d29160458..b21a28d8e 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -231,7 +231,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
         const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
 
         const script = ScriptCast(this.rootDoc.script);
-        if (!script) { return null };
+        if (!script) { return null; }
 
         let noviceList: string[] = [];
         let text: string | undefined;
@@ -459,7 +459,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
         const buttonText = StrCast(this.rootDoc.buttonText);
 
         // TODO:glr Add label of button type
-        let button = this.defaultButton;
+        let button: JSX.Element | null = this.defaultButton;
 
         switch (this.type) {
             case ButtonType.TextButton:
@@ -527,9 +527,10 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
         }
 
         return !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? button :
-            <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>
-                {button}
-            </Tooltip>;
+            button !== null ?
+                <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>
+                    {button}
+                </Tooltip > : null
     }
 }
 
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 6a3f9ed00..bb3791f1e 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -86,7 +86,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
     public static fieldContent(textBoxDoc: Doc, dashDoc: Doc, fieldKey: string) {
         const dashVal = dashDoc[fieldKey] ?? dashDoc[DataSym][fieldKey] ?? (fieldKey === "PARAMS" ? textBoxDoc[fieldKey] : "");
         const fval = dashVal instanceof List ? dashVal.join(DashFieldViewInternal.multiValueDelimeter) : StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(textBoxDoc)[fieldKey] : dashVal;
-        return { boolVal: Cast(fval, "boolean", null), strVal: Field.toString(fval as Field) || "" }
+        return { boolVal: Cast(fval, "boolean", null), strVal: Field.toString(fval as Field) || "" };
     }
 
     // set the display of the field's value (checkbox for booleans, span of text for strings)
@@ -241,8 +241,8 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
         const hideMenu = () => {
             this.fadeOut(true);
             document.removeEventListener("pointerdown", hideMenu);
-        }
-        document.addEventListener("pointerdown", hideMenu)
+        };
+        document.addEventListener("pointerdown", hideMenu);
     }
     render() {
         const buttons = [
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ce82821b6..434fccf8d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1202,7 +1202,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 const tr = this._editorView.state.tr.setStoredMarks(storedMarks).insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size).setStoredMarks(storedMarks);
                 this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
             } else if (this._editorView && curText && !FormattedTextBox.DontSelectInitialText) {
-                selectAll(this._editorView.state, this._editorView?.dispatch)
+                selectAll(this._editorView.state, this._editorView?.dispatch);
                 this.startUndoTypingBatch();
             } else if (this._editorView) {
                 this._editorView.dispatch(this._editorView.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index f4e792acd..0af7715fe 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -2435,9 +2435,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             indexNum += (index[i] * (10 ** (-i)));
         }
         this._treeViewMap.set(indexNum, treeViewDoc);
-        console.log(String(index), treeViewDoc)
         this.props.Document.presentationLinearizedDocuments = new List<Doc>(this.sort(this._treeViewMap)); // this is a flat array of Docs
-        console.log(this.props.Document.presentationLinearizedDocuments)
         return this.childDocs;
     }
 
@@ -2447,21 +2445,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             indexNum += (index[i] * (10 ** (-i)));
         }
         console.log(String(index), treeViewDoc)
-        this._treeViewMap.delete(indexNum)
+        this._treeViewMap.delete(indexNum);
         this.props.Document.presentationLinearizedDocuments = new List<Doc>(this.sort(this._treeViewMap));
         return this.childDocs;
     }
 
     // TODO: [AL] implement sort function for an array of numbers (e.g. arr[1,2,4] v arr[1,2,1])
-    sort = (treeViewMap: Map<number, Doc>): Doc[] => {
-        // TODO
-        const sortedMap = [...treeViewMap.entries()].sort();
-        var sortedDocs = [];
-        for (var kv of sortedMap) {
-            sortedDocs.push(kv[1]);
-        }
-        return sortedDocs;
-    }
+    sort = (treeViewMap: Map<number, Doc>) => [...treeViewMap.entries()].sort().map(kv => kv[1]);
 
     render() {
         // calling this method for keyEvents
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 6db36f36a..d5fd425ad 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -154,7 +154,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         if (doc !== this.props.rootDoc && mainCont) {
             const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
             const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, .1 * windowHeight, NumCast(this.props.Document.scrollHeight));
-            if (scrollTo !== undefined && scrollTo != this.props.layoutDoc._scrollTop) {
+            if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) {
                 focusSpeed = 500;
 
                 if (!this._pdfViewer) this._initialScroll = scrollTo;
-- 
cgit v1.2.3-70-g09d2


From c99825362f4a221af728f1dcee139c7403a7916f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 6 Jun 2022 13:13:49 -0400
Subject: added auto links from recordings to pdfs when they scroll.   opened
 audio links on right by default instead of in lightbox.  focus on
 pdf/coponent anchor when following link from it (via the LinkMenu)

---
 src/client/documents/Documents.ts                          | 7 +++++--
 src/client/views/InkTangentHandles.tsx                     | 3 +--
 src/client/views/collections/CollectionStackedTimeline.tsx | 1 +
 src/client/views/linking/LinkMenuItem.tsx                  | 4 ++++
 src/client/views/pdf/PDFViewer.tsx                         | 7 +++++++
 5 files changed, 18 insertions(+), 4 deletions(-)

(limited to 'src/client/views/pdf/PDFViewer.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2ca9cdb71..817478b2f 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1097,8 +1097,11 @@ export namespace DocUtils {
 
     export function MakeLinkToActiveAudio(getSourceDoc: () => Doc, broadcastEvent = true) {
         broadcastEvent && runInAction(() => DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1);
-        return DocUtils.ActiveRecordings.map(audio =>
-            DocUtils.MakeLink({ doc: getSourceDoc() }, { doc: audio.getAnchor() || audio.props.Document }, "recording annotation:linked recording", "recording timeline"));
+        return DocUtils.ActiveRecordings.map(audio => {
+            const link = DocUtils.MakeLink({ doc: getSourceDoc() }, { doc: audio.getAnchor() || audio.props.Document }, "recording annotation:linked recording", "recording timeline");
+            link && (link.followLinkLocation = "add:right");
+            return link;
+        });
     }
 
     export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) {
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
index b15e4260d..ae35bc980 100644
--- a/src/client/views/InkTangentHandles.tsx
+++ b/src/client/views/InkTangentHandles.tsx
@@ -13,7 +13,6 @@ import { Colors } from "./global/globalEnums";
 import { InkingStroke } from "./InkingStroke";
 import { InkStrokeProperties } from "./InkStrokeProperties";
 import { DocumentView } from "./nodes/DocumentView";
-
 export interface InkHandlesProps {
     inkDoc: Doc;
     inkView: DocumentView;
@@ -103,7 +102,7 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
                             r={screenSpaceLineWidth * 2}
                             fill={Colors.MEDIUM_BLUE}
                             strokeWidth={1}
-                            stroke={Colors.MEDIUM_BLUE}
+                            stroke={Colors.BLACK}
                             onPointerDown={e => this.onHandleDown(e, pts.I)}
                             pointerEvents="all"
                             cursor="default"
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index ca0b9d3d3..ebdea9aaf 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -444,6 +444,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
             _stayInCollection: true,
             useLinkSmallAnchor: true,
             hideLinkButton: true,
+            _isLinkButton: true,
             annotationOn: rootDoc,
             _timelineLabel: true,
             borderRounding: anchorEndTime === undefined ? "100%" : undefined
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 3ddcf803d..a75e7a0c4 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -102,6 +102,10 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
 
                     this.props.itemHandler?.(this.props.linkDoc);
                 } else {
+                    const focusDoc = Cast(this.props.linkDoc.anchor1, Doc, null)?.annotationOn === this.props.sourceDoc ? Cast(this.props.linkDoc.anchor1, Doc, null) :
+                        Cast(this.props.linkDoc.anchor2, Doc, null)?.annotationOn === this.props.sourceDoc ? Cast(this.props.linkDoc.anchor12, Doc, null) : undefined;
+
+                    if (focusDoc) this.props.docView.ComponentView?.scrollFocus?.(focusDoc, true);
                     LinkManager.FollowLink(this.props.linkDoc, this.props.sourceDoc, this.props.docView.props, false);
                 }
             });
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index f706be6c6..0ded1bb3c 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -274,6 +274,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
         }
     }
 
+    @observable private _scrollTimer: any;
+
     onScroll = (e: React.UIEvent<HTMLElement>) => {
         if (this._mainCont.current && !this._forcedScroll) {
             this._ignoreScroll = true; // the pdf scrolled, so we need to tell the Doc to scroll but we don't want the doc to then try to set the PDF scroll pos (which would interfere with the smooth scroll animation)
@@ -281,6 +283,11 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 this.props.layoutDoc._scrollTop = this._mainCont.current.scrollTop;
             }
             this._ignoreScroll = false;
+            if (this._scrollTimer) clearTimeout(this._scrollTimer); // wait until a scrolling pause, then create an anchor to audio
+            this._scrollTimer = setTimeout(() => {
+                DocUtils.MakeLinkToActiveAudio(() => this.props.DocumentView?.().ComponentView?.getAnchor!()!, false);
+                this._scrollTimer = undefined;
+            }, 200);
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From b292a055401af6236e0537cfad603016d77a535a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 20 Jun 2022 13:40:07 -0400
Subject: a bunch of changes to make some userDoc() field accesses more
 typesafe by going through CurrentUserUtils.  Some normalization of
 naming/functionality in CurrentUserUtils

---
 src/client/apis/youtube/YoutubeBox.tsx             |   2 +-
 src/client/documents/Documents.ts                  |   6 +-
 src/client/util/CurrentUserUtils.ts                | 118 +++++++++++----------
 src/client/util/SettingsManager.tsx                |  16 +--
 src/client/util/SharingManager.tsx                 |   4 +-
 src/client/views/DashboardView.tsx                 |   4 +-
 src/client/views/DocComponent.tsx                  |   6 +-
 src/client/views/DocumentButtonBar.tsx             |   2 +-
 src/client/views/GestureOverlay.tsx                |  12 +--
 src/client/views/GlobalKeyHandler.ts               |   8 +-
 src/client/views/InkStrokeProperties.ts            |   4 +-
 src/client/views/InkTranscription.tsx              |   2 +-
 src/client/views/InkingStroke.tsx                  |   2 +-
 src/client/views/MainView.tsx                      |   4 +-
 src/client/views/OverlayView.tsx                   |   8 +-
 src/client/views/PropertiesButtons.tsx             |   4 +-
 src/client/views/PropertiesView.tsx                |  86 ++++++++-------
 src/client/views/TemplateMenu.tsx                  |  11 +-
 .../views/collections/CollectionDockingView.tsx    |  22 ++--
 src/client/views/collections/CollectionMenu.tsx    |  35 +++---
 .../views/collections/CollectionStackingView.tsx   |   2 +-
 .../CollectionStackingViewFieldColumn.tsx          |   4 +-
 .../views/collections/CollectionTreeView.tsx       |   8 +-
 src/client/views/collections/CollectionView.tsx    |  14 +--
 src/client/views/collections/TabDocView.tsx        |   2 +-
 src/client/views/collections/TreeView.tsx          |   6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  30 +++---
 .../collections/collectionFreeForm/MarqueeView.tsx |   6 +-
 .../collectionSchema/CollectionSchemaHeaders.tsx   |   2 +-
 src/client/views/nodes/AudioBox.tsx                |   5 +-
 src/client/views/nodes/ColorBox.tsx                |   3 +-
 src/client/views/nodes/DocumentView.tsx            |  40 +++----
 src/client/views/nodes/FilterBox.tsx               |  47 ++++----
 src/client/views/nodes/ImageBox.tsx                |   4 +-
 src/client/views/nodes/LabelBox.tsx                |   2 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   4 +-
 src/client/views/nodes/VideoBox.tsx                |   6 +-
 src/client/views/nodes/WebBox.tsx                  |   6 +-
 src/client/views/nodes/button/FontIconBox.tsx      |  35 +++---
 .../views/nodes/formattedText/FormattedTextBox.tsx |  12 +--
 .../views/nodes/formattedText/RichTextMenu.tsx     |   1 -
 src/client/views/nodes/trails/PresBox.tsx          |  30 +++---
 src/client/views/nodes/trails/PresElementBox.tsx   |  16 +--
 src/client/views/pdf/PDFViewer.tsx                 |   2 +-
 src/client/views/topbar/TopBar.tsx                 |  10 +-
 src/client/views/webcam/DashWebRTCVideo.tsx        |   2 +-
 src/fields/Doc.ts                                  |  18 ++--
 src/fields/util.ts                                 |   2 +-
 src/mobile/MobileInterface.tsx                     |  20 ++--
 49 files changed, 360 insertions(+), 335 deletions(-)

(limited to 'src/client/views/pdf/PDFViewer.tsx')

diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
index c5ff2db68..e14dc60b4 100644
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ b/src/client/apis/youtube/YoutubeBox.tsx
@@ -351,7 +351,7 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
 
         const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting;
 
-        const classname = "webBox-cont" + (this.props.isSelected() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
+        const classname = "webBox-cont" + (this.props.isSelected() && CurrentUserUtils.ActiveTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
         return (
             <>
                 <div className={classname}  >
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 927996af2..f96bffb3c 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -687,7 +687,7 @@ export namespace Docs {
             ![DocumentType.LINK, DocumentType.MARKER, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc);
 
             !Doc.IsSystem(dataDoc) && ![DocumentType.MARKER, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(proto.type as any) &&
-                !dataDoc.isFolder && !dataProps.annotationOn && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", dataDoc);
+                !dataDoc.isFolder && !dataProps.annotationOn && Doc.AddDocToList(CurrentUserUtils.MyFileOrphans, undefined, dataDoc);
 
             updateCachedAcls(dataDoc);
             updateCachedAcls(viewDoc);
@@ -802,7 +802,7 @@ export namespace Docs {
             I.rotation = 0;
             I.data = new InkField(points);
             I.creationDate = new DateField;
-            I["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
+            I["acl-Public"] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
             I["acl-Override"] = "None";
             I.links = ComputedField.MakeFunction("links(self)");
             I[Initializing] = false;
@@ -1204,7 +1204,7 @@ export namespace DocUtils {
             created = Docs.Create.RecordingDocument((field).url.href, resolved);
             layout = RecordingBox.LayoutString;
         } else if (field instanceof InkField) {
-            created = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), (field).inkData, resolved);
+            created = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), (field).inkData, resolved);
             layout = InkingStroke.LayoutString;
         } else if (field instanceof List && field[0] instanceof Doc) {
             created = Docs.Create.StackingDocument(DocListCast(field), resolved);
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index afb1442bf..7a38886fe 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -74,16 +74,16 @@ export class CurrentUserUtils {
     @observable public static headerBarHeight: number = 0;
     @observable public static searchPanelWidth: number = 0;
 
-    static AssignScripts(doc:Doc, scripts?:{ [key: string]: string;}, functions?:{[key:string]: string}) {
+    static AssignScripts(doc:Doc, scripts?:{ [key: string]: string;}, funcs?:{[key:string]: string}) {
         scripts && Object.keys(scripts).map(key => {
             if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) {
                 doc[key] = ScriptField.MakeScript(scripts[key], { dragData: DragManager.DocumentDragData.name,  value:"any", scriptContext: "any" }, {"_readOnly_": true});
             }
         }); 
-        functions && Object.keys(functions).map(key => {
+        funcs && Object.keys(funcs).map(key => {
             const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
-            if (ScriptCast(cfield)?.script.originalScript !== functions[key] && functions[key]) {
-                doc[key] = ComputedField.MakeFunction(functions[key], { dragData: DragManager.DocumentDragData.name }, {"_readOnly_": true});
+            if (ScriptCast(cfield)?.script.originalScript !== funcs[key] && funcs[key]) {
+                doc[key] = ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, {"_readOnly_": true});
             }
         });
         return doc;
@@ -180,14 +180,14 @@ export class CurrentUserUtils {
     }
 
     /// Initializes collection of templates for notes and click functions
-    static setupDocTemplates(doc: Doc) {
+    static setupDocTemplates(doc: Doc, field="myTemplates") {
         const templates = [
             CurrentUserUtils.setupNoteTemplates(doc),
             CurrentUserUtils.setupClickEditorTemplates(doc)
         ];
         const reqdOpts = { title: "template layouts", _xMargin: 0, system: true,  };
         const reqdScripts = {  dropConverter: "convertToButtons(dragData)"  };
-        return this.AssignScripts(this.AssignOpts(DocCast(doc.templateDocs), reqdOpts, templates) ?? (doc.templateDocs = Docs.Create.TreeDocument(templates, reqdOpts)), reqdScripts);
+        return this.AssignScripts(this.AssignOpts(DocCast(doc[field]), reqdOpts, templates) ?? (doc[field] = Docs.Create.TreeDocument(templates, reqdOpts)), reqdScripts);
     }
 
     // setup templates for different document types when they are iconified from Document Decorations
@@ -329,7 +329,7 @@ export class CurrentUserUtils {
     static setupCreatorButtons(doc: Doc, dragCreatorDoc?:Doc):Doc {
         const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => {
             const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined;
-            const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["backgroundColor"]).omit,
+            const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit,
                 _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, _dropAction: "alias",
                 btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, system: true,
                 _removeDropProperties: new List<string>(["_stayInCollection"]),
@@ -562,6 +562,7 @@ export class CurrentUserUtils {
         if (Cast(myDashboards.childContextMenuFilters, listSpec(ScriptField), null)?.length !== childContextMenuFilters.length) {
             myDashboards.childContextMenuFilters = new List<ScriptField>(childContextMenuFilters.map(script => !script ? script: ScriptField.MakeFunction(script)!));
         }
+        return myDashboards;
     }
 
     /// initializes the left sidebar Trails pane
@@ -728,13 +729,13 @@ export class CurrentUserUtils {
 
     static inkTools():Button[] {
         return [
-            { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", scripts:{onClick:'{ return setActiveInkTool("pen", _readOnly_);}' }},
-            { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts:{onClick:'{ return setActiveInkTool("write", _readOnly_);}'} },
-            { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts:{onClick:'{ return setActiveInkTool("eraser", _readOnly_);}' }},
-            // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveInkTool("highlighter")'} },
-            { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", scripts:{onClick:'{ return setActiveInkTool("circle", _readOnly_);}'} },
-            // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveInkTool("square")' },
-            { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", scripts:{onClick: '{ return setActiveInkTool("line", _readOnly_);}' }},
+            { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", scripts:{onClick:'{ return setActiveTool("pen", _readOnly_);}' }},
+            { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts:{onClick:'{ return setActiveTool("write", _readOnly_);}'} },
+            { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts:{onClick:'{ return setActiveTool("eraser", _readOnly_);}' }},
+            // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveTool("highlighter")'} },
+            { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", scripts:{onClick:'{ return setActiveTool("circle", _readOnly_);}'} },
+            // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveTool("square")' },
+            { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", scripts:{onClick: '{ return setActiveTool("line", _readOnly_);}' }},
             { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", scripts: {script: "{ return setFillColor(value, _readOnly_);}"} },
             { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, numBtnMin: 1, ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'} },
             { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, scripts: {script: '{ return setStrokeColor(value, _readOnly_);}'} },
@@ -941,41 +942,39 @@ export class CurrentUserUtils {
     }
 
     static async updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
-        if (!doc.globalGroupDatabase) doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument();
-        await DocListCastAsync((doc.globalGroupDatabase as Doc).data);
-        reaction(() => DateCast((doc.globalGroupDatabase as Doc)["data-lastModified"]),
+        doc.globalGroupDatabase ?? (doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument());
+        await DocListCastAsync(DocCast(doc.globalGroupDatabase).data);
+        reaction(() => DateCast(DocCast(doc.globalGroupDatabase)["data-lastModified"]),
             async () => {
-                const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data);
+                const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data);
                 const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || [];
                 SnappingManager.SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]);
             }, { fireImmediately: true });
         // Document properties on load
-        doc.system = true;
-        doc.title = Doc.CurrentUserEmail;
-        doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode;
-        doc._raiseWhenDragged = true;
-        doc._showLabel = true;
-        doc._showMenuLabel = true;
-        doc.textAlign = StrCast(doc.textAlign, "left");
-        doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)");
-        doc.activeInkWidth = Number(StrCast(doc.activeInkWidth, "1"));
-        doc.activeInkBezier = StrCast(doc.activeInkBezier, "0");
-        doc.activeFillColor = StrCast(doc.activeFillColor, "");
-        doc.activeArrowStart = StrCast(doc.activeArrowStart, "");
-        doc.activeArrowEnd = StrCast(doc.activeArrowEnd, "");
-        doc.activeDash = StrCast(doc.activeDash, "0");
-        doc.fontSize = StrCast(doc.fontSize, "12px");
-        doc.fontFamily = StrCast(doc.fontFamily, "Arial");
-        doc.fontColor = StrCast(doc.fontColor, "black");
-        doc.fontHighlight = StrCast(doc.fontHighlight, "");
-        doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, false);
-        doc.activeCollectionNestedBackground = Cast(doc.activeCollectionNestedBackground, "string", null);
-        doc.noviceMode = BoolCast(doc.noviceMode, true);
-        !doc.savedFilters && (doc.savedFilters = new List<Doc>());
+        doc.system ?? (doc.system = true);
+        doc.title ?? (doc.title = Doc.CurrentUserEmail);
+        Doc.noviceMode ?? (Doc.noviceMode = true);
+        doc._raiseWhenDragged ?? (doc._raiseWhenDragged = true);
+        doc._showLabel ?? (doc._showLabel = true);
+        doc._showMenuLabel ?? (doc._showMenuLabel = true);
+        doc.textAlign ?? (doc.textAlign = "left");
+        doc.activeInkColor ?? (doc.activeInkColor = "rgb(0, 0, 0)");;
+        doc.activeInkWidth ?? (doc.activeInkWidth = 1);
+        doc.activeInkBezier ?? (doc.activeInkBezier = "0");
+        doc.activeFillColor ?? (doc.activeFillColor = "");
+        doc.activeArrowStart ?? (doc.activeArrowStart = "");
+        doc.activeArrowEnd ?? (doc.activeArrowEnd = "");
+        doc.activeDash ?? (doc.activeDash == "0");
+        doc.fontSize ?? (doc.fontSize = "12px");
+        doc.fontFamily ?? (doc.fontFamily = "Arial");
+        doc.fontColor ?? (doc.fontColor = "black");
+        doc.fontHighlight ?? (doc.fontHighlight = "");
+        doc.defaultAclPrivate ?? (doc.defaultAclPrivate = false);
+        doc.savedFilters ?? (doc.savedFilters = new List<Doc>());
         doc.filterDocCount = 0;
         doc.freezeChildren = "remove|add";
-        doc.myPublishedDocs = doc.myPublishedDocs ?? new List<Doc>();
-        doc.myHeaderBar = doc.myHeaderBar?? Docs.Create.MulticolumnDocument([], { title: "header bar", system: true }); // drop down panel at top of dashboard for stashing documents
+        doc.myPublishedDocs ?? (doc.myPublishedDocs = new List<Doc>());
+        doc.myHeaderBar ?? (doc.myHeaderBar = Docs.Create.MulticolumnDocument([], { title: "header bar", system: true })); // drop down panel at top of dashboard for stashing documents
         await this.setupLinkDocs(doc, linkDatabaseId);
         await this.setupSharedDocs(doc, sharingDocumentId);  // sets up the right sidebar collection for mobile upload documents and sharing
         this.setupDefaultIconTemplates(doc);  // creates a set of icon templates triggered by the document deoration icon
@@ -985,7 +984,7 @@ export class CurrentUserUtils {
         this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected
         this.setupDockedButtons(doc);  // the bottom bar of font icons
         this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left
-        if (!doc.globalScriptDatabase) doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument();
+        doc.globalScriptDatabase ?? ( doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument());
 
         setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500);
         doc.fieldInfos = await Docs.setupFieldInfos();
@@ -1022,7 +1021,7 @@ export class CurrentUserUtils {
                     const userDoc = Docs.newAccount ? new Doc(userDocumentId, true) : field as Doc;
                     Docs.newAccount &&(userDoc.activePage = "home");
                     const updated = this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId);
-                    (await DocListCastAsync(Cast(Doc.UserDoc().myLinkDatabase, Doc, null)?.data))?.forEach(async link => { // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager
+                    (await DocListCastAsync(Doc.LinkDBDoc()?.data))?.forEach(async link => { // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager
                         const a1 = await Cast(link?.anchor1, Doc, null);
                         const a2 = await Cast(link?.anchor2, Doc, null);
                     });
@@ -1097,7 +1096,7 @@ export class CurrentUserUtils {
                     }
                 }
             } else if (input.files && input.files.length !== 0) {
-                const importDocs = Cast(Doc.UserDoc().myImportDocs, Doc, null);
+                const importDocs = CurrentUserUtils.MyImports;
                 const disposer = OverlayView.ShowSpinner();
                 DocListCastAsync(importDocs.data).then(async list => {
                     const results = await DocUtils.uploadFilesToDocs(Array.from(input.files || []), {});
@@ -1115,9 +1114,10 @@ export class CurrentUserUtils {
     }
 
     public static CaptureDashboardThumbnail() {
+        const activeDashboard = CurrentUserUtils.ActiveDashboard;
         const docView = CollectionDockingView.Instance.props.DocumentView?.();
         const content = docView?.ContentDiv;
-        if (docView && content) {
+        if (docView && content && activeDashboard) {
             const _width = Number(getComputedStyle(content).width.replace("px",""));
             const _height = Number(getComputedStyle(content).height.replace("px",""));
             return CollectionFreeFormView.UpdateIcon(
@@ -1130,20 +1130,22 @@ export class CurrentUserUtils {
                     const proto = Cast(img.proto, Doc, null)!;
                     proto["data-nativeWidth"] = _width;
                     proto["data-nativeHeight"] = _height;
-                    Doc.GetProto(CurrentUserUtils.ActiveDashboard).thumb = img;
+                    Doc.GetProto(activeDashboard).thumb = img;
                 });
         }
 
     }
 
     public static async snapshotDashboard(userDoc: Doc) {
-        const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard);
-        Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", copy);
-        CurrentUserUtils.openDashboard(userDoc, copy);
+        if (CurrentUserUtils.ActiveDashboard) {
+            const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard);
+            Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", copy);
+            CurrentUserUtils.openDashboard(userDoc, copy);
+        }
     }
 
     public static closeActiveDashboard = () => {
-          Doc.UserDoc().activeDashboard = undefined;
+        CurrentUserUtils.ActiveDashboard = undefined;
     }
 
     public static createNewDashboard = async (userDoc: Doc, id?: string) => {
@@ -1195,6 +1197,8 @@ export class CurrentUserUtils {
     public static get MyHeaderBar() { return DocCast(Doc.UserDoc().myHeaderBar); }
     public static get MyTools() { return DocCast(Doc.UserDoc().myTools); }
     public static get MyDashboards() { return DocCast(Doc.UserDoc().myDashboards); }
+    public static get MyFileOrphans() { return DocCast(Doc.UserDoc().myFileOrphans); }
+    public static get MyTemplates() { return DocCast(Doc.UserDoc().myTemplates); }
     public static get MyLeftSidebarMenu() { return DocCast(Doc.UserDoc().myLeftSidebarMenu); }
     public static get MyLeftSidebarPanel() { return DocCast(Doc.UserDoc().myLeftSidebarPanel); }
     public static get MySharedDocs() { return DocCast(Doc.UserDoc().mySharedDocs); }
@@ -1202,12 +1206,16 @@ export class CurrentUserUtils {
     public static get MyImports() { return DocCast(Doc.UserDoc().myImports); }
     public static get MyContextMenuBtns() { return DocCast(Doc.UserDoc().myContextMenuBtns); }
     public static get MyRecentlyClosed() { return DocCast(Doc.UserDoc().myRecentlyClosed); }
-    public static get MyOverlayDocs() { return DocListCast(DocCast(Doc.UserDoc().myOverlayDocs)?.data); }
+    public static get MyOverlayDocs() { return DocCast(Doc.UserDoc().myOverlayDocs); }
     public static get ActiveDashboard() { return DocCast(Doc.UserDoc().activeDashboard); }
+    public static set ActiveDashboard(val:Doc|undefined) { Doc.UserDoc().activeDashboard = val; }
     public static get ActivePresentation() { return DocCast(Doc.UserDoc().activePresentation); }
+    public static set ActivePresentation(val) { Doc.UserDoc().activePresentation = val; }
+    public static get ActivePage() { return StrCast(Doc.UserDoc().activePage); }
+    public static set ActivePage(val) { Doc.UserDoc().activePage = val; }
     public static get EmptyPane() { return DocCast(Doc.UserDoc().emptyPane); }
-    public static set SelectedTool(tool: InkTool) { Doc.UserDoc().activeInkTool = tool; }
-    @computed public static get SelectedTool(): InkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkTool; }
+    public static set ActiveTool(tool: InkTool) { Doc.UserDoc().activeTool = tool; }
+    public static get ActiveTool(): InkTool { return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool; }
 }
 
 ScriptingGlobals.add(function openDragFactory(dragFactory: Doc) {
@@ -1219,7 +1227,7 @@ ScriptingGlobals.add(function openDragFactory(dragFactory: Doc) {
     }
 });
 ScriptingGlobals.add(function MySharedDocs() { return CurrentUserUtils.MySharedDocs; }, "document containing all shared Docs");
-ScriptingGlobals.add(function IsNoviceMode() { return Doc.UserDoc().noviceMode; }, "is Dash in novice mode");
+ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode");
 ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering");
 ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); }, "creates a snapshot copy of a dashboard");
 ScriptingGlobals.add(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); }, "creates a new dashboard when called");
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 6a26dfdc7..382274462 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -38,7 +38,7 @@ export class SettingsManager extends React.Component<{}> {
     @observable activeTab = "Accounts";
 
     @computed get backgroundColor() { return Doc.UserDoc().activeCollectionBackground; }
-    @computed get colorScheme() { return CurrentUserUtils.ActiveDashboard.colorScheme; }
+    @computed get colorScheme() { return CurrentUserUtils.ActiveDashboard?.colorScheme; }
 
     constructor(props: {}) {
         super(props);
@@ -59,7 +59,7 @@ export class SettingsManager extends React.Component<{}> {
         }
     }
 
-    @undoBatch selectUserMode = action((e: React.ChangeEvent) => Doc.UserDoc().noviceMode = (e.currentTarget as any)?.value === "Novice");
+    @undoBatch selectUserMode = action((e: React.ChangeEvent) => Doc.noviceMode = (e.currentTarget as any)?.value === "Novice");
     @undoBatch changeShowTitle = action((e: React.ChangeEvent) => Doc.UserDoc().showTitle = (e.currentTarget as any).value ? "title" : undefined);
     @undoBatch changeFontFamily = action((e: React.ChangeEvent) => Doc.UserDoc().fontFamily = (e.currentTarget as any).value);
     @undoBatch changeFontSize = action((e: React.ChangeEvent) => Doc.UserDoc().fontSize = (e.currentTarget as any).value);
@@ -78,19 +78,21 @@ export class SettingsManager extends React.Component<{}> {
     @undoBatch
     @action
     changeColorScheme = action((e: React.ChangeEvent) => {
+        const activeDashboard= CurrentUserUtils.ActiveDashboard;
+        if (!activeDashboard) return;
         const scheme: ColorScheme = (e.currentTarget as any).value;
         switch (scheme) {
             case ColorScheme.Light:
-                CurrentUserUtils.ActiveDashboard.colorScheme = undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
+                activeDashboard.colorScheme = undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
                 addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "#d3d3d3 !important" });
                 break;
             case ColorScheme.Dark:
-                CurrentUserUtils.ActiveDashboard.colorScheme = ColorScheme.Dark;
+                activeDashboard.colorScheme = ColorScheme.Dark;
                 addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "black !important" });
                 break;
             case ColorScheme.System: default:
                 window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
-                    CurrentUserUtils.ActiveDashboard.colorScheme = e.matches ? ColorScheme.Dark : undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
+                    activeDashboard.colorScheme = e.matches ? ColorScheme.Dark : undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
                 });
                 break;
         }
@@ -254,7 +256,7 @@ export class SettingsManager extends React.Component<{}> {
             <div className="tab-column">
                 <div className="tab-column-title">Modes</div>
                 <div className="tab-column-content">
-                    <select className="modes-select" onChange={this.selectUserMode} defaultValue={Doc.UserDoc().noviceMode ? "Novice" : "Developer"}>
+                    <select className="modes-select" onChange={this.selectUserMode} defaultValue={Doc.noviceMode ? "Novice" : "Developer"}>
                         <option key={"Novice"} value={"Novice"}> Novice </option>
                         <option key={"Developer"} value={"Developer"}> Developer</option>
                     </select>
@@ -270,7 +272,7 @@ export class SettingsManager extends React.Component<{}> {
                     <button onClick={() => GroupManager.Instance?.open()}>Manage groups</button>
                     <div className="default-acl">
                         <input className="acl-check" type="checkbox" checked={BoolCast(Doc.UserDoc()?.defaultAclPrivate)}
-                            onChange={action(() => Doc.UserDoc().defaultAclPrivate = !Doc.UserDoc().defaultAclPrivate)} />
+                            onChange={action(() => Doc.defaultAclPrivate = !Doc.defaultAclPrivate)} />
                         <div className="acl-text">Default access private</div>
                     </div>
                 </div>
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index f439d4488..d77633b8d 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -369,7 +369,7 @@ export class SharingManager extends React.Component<{}> {
         const dropdownValues: string[] = Object.values(SharingPermissions);
         if (!uniform) dropdownValues.unshift("-multiple-");
         if (override) dropdownValues.unshift("None");
-        return dropdownValues.filter(permission => !Doc.UserDoc().noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission =>
+        return dropdownValues.filter(permission => !Doc.noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission =>
         (
             <option key={permission} value={permission}>
                 {permission}
@@ -680,7 +680,7 @@ export class SharingManager extends React.Component<{}> {
                         </div>
 
                         <div className="acl-container">
-                            {Doc.UserDoc().noviceMode ? (null) :
+                            {Doc.noviceMode ? (null) :
                                 <div className="layoutDoc-acls">
                                     <input type="checkbox" onChange={action(() => this.layoutDocAcls = !this.layoutDocAcls)} checked={this.layoutDocAcls} /> <label>Layout</label>
                                 </div>}
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index efc1644fe..9a0f25fe3 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -34,8 +34,8 @@ export class DashboardView extends React.Component {
 
       clickDashboard = async (e: React.MouseEvent, dashboard: Doc) => {
             if (e.detail === 2) {
-                  Doc.UserDoc().activeDashboard = dashboard;
-                  Doc.UserDoc().activePage = "dashboard";
+                  CurrentUserUtils.ActiveDashboard = dashboard;
+                  CurrentUserUtils.ActivePage = "dashboard";
             }
       }
 
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index be5927497..169bd3873 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -65,7 +65,7 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
 
         isContentActive = (outsideReaction?: boolean) => (
             this.props.isContentActive?.() === false ? false :
-                (CurrentUserUtils.SelectedTool !== InkTool.None ||
+                (CurrentUserUtils.ActiveTool !== InkTool.None ||
                     (this.props.isContentActive?.() || this.props.Document.forceActive ||
                         this.props.isSelected(outsideReaction) ||
                         this.props.rootSelected(outsideReaction)) ? true : undefined))
@@ -200,7 +200,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
 
                     if (effectiveAcl === AclAugment) {
                         added.map(doc => {
-                            if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc);
+                            if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)) && CurrentUserUtils.ActiveDashboard) inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc);
                             doc.context = this.props.Document;
                             if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document;
                             Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
@@ -213,7 +213,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
                             doc.context = this.props.Document;
                             if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document;
 
-                            inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc);
+                            CurrentUserUtils.ActiveDashboard && inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc);
                         });
                         const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List<Doc>;
                         if (annoDocs instanceof List) annoDocs.push(...added);
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 0bbe473d7..9b8f7238d 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -347,7 +347,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
             <div className="documentButtonBar-button">
                 {this.menuButton}
             </div>
-            {/* {Doc.UserDoc().noviceMode ? (null) : <div className="documentButtonBar-button">
+            {/* {Doc.noviceMode ? (null) : <div className="documentButtonBar-button">
                 {this.moreButton}
             </div>} */}
         </div>;
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 6afe64868..e960f5cca 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -129,7 +129,7 @@ export class GestureOverlay extends Touchable {
             // and this seems to be the only way of differentiating pen and touch on touch events
             if (pt.radiusX > 1 && pt.radiusY > 1) {
                 InkTranscription.Instance.createInkGroup();
-                Doc.UserDoc().activeInkTool = InkTool.None;
+                CurrentUserUtils.ActiveTool = InkTool.None;
                 this.prevPoints.set(pt.identifier, pt);
             }
         }
@@ -498,18 +498,18 @@ export class GestureOverlay extends Touchable {
             setupMoveUpEvents(this, e, returnFalse, returnFalse, action((e: PointerEvent, doubleTap?: boolean) => {
                 if (doubleTap) {
                     InkTranscription.Instance.createInkGroup();
-                    CurrentUserUtils.SelectedTool = InkTool.None;
+                    CurrentUserUtils.ActiveTool = InkTool.None;
                     return;
                 }
             }));
         }
-        if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) {
+        if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) {
             if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
-                CurrentUserUtils.SelectedTool = InkTool.Write;
+                CurrentUserUtils.ActiveTool = InkTool.Write;
             }
             this._points.push({ X: e.clientX, Y: e.clientY });
             setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
-            // if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)");
+            // if (CurrentUserUtils.ActiveTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)");
         }
     }
 
@@ -606,7 +606,7 @@ export class GestureOverlay extends Touchable {
                 this._points = [];
                 if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) {
                     this.InkShape = "";
-                    Doc.UserDoc().activeInkTool = InkTool.None;
+                    CurrentUserUtils.ActiveTool = InkTool.None;
                 }
             }
             // if we're not drawing in a toolglass try to recognize as gesture
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 16d7c9a23..f5122df3f 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -57,7 +57,7 @@ export class KeyManager {
 
     public handle = action((e: KeyboardEvent) => {
         if (e.key?.toLowerCase() === "shift") DocumentDecorations.Instance.AddToSelection = true;
-        //if (!Doc.UserDoc().noviceMode && e.key.toLocaleLowerCase() === "shift") DocServer.UPDATE_SERVER_CACHE(true);
+        //if (!Doc.noviceMode && e.key.toLocaleLowerCase() === "shift") DocServer.UPDATE_SERVER_CACHE(true);
         const keyname = e.key && e.key.toLowerCase();
         this.handleGreedy(keyname);
 
@@ -112,7 +112,7 @@ export class KeyManager {
                 DocumentLinksButton.StartLink = undefined;
                 DocumentLinksButton.StartLinkView = undefined;
                 InkStrokeProperties.Instance._controlButton = false;
-                CurrentUserUtils.SelectedTool = InkTool.None;
+                CurrentUserUtils.ActiveTool = InkTool.None;
                 DragManager.CompleteWindowDrag?.(true);
                 var doDeselect = true;
                 if (SnappingManager.GetIsDragging()) {
@@ -241,9 +241,9 @@ export class KeyManager {
                     }
                 }
                 break;
-            case "e": CurrentUserUtils.SelectedTool = InkTool.Eraser;
+            case "e": CurrentUserUtils.ActiveTool = InkTool.Eraser;
                 break;
-            case "p": CurrentUserUtils.SelectedTool = InkTool.Pen;
+            case "p": CurrentUserUtils.ActiveTool = InkTool.Pen;
                 break;
             case "o":
                 const target = SelectionManager.Docs().lastElement();
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 0449da483..471ad09e9 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -25,8 +25,8 @@ export class InkStrokeProperties {
 
     constructor() {
         InkStrokeProperties._Instance = this;
-        reaction(() => this._controlButton, button => button && (CurrentUserUtils.SelectedTool = InkTool.None));
-        reaction(() => CurrentUserUtils.SelectedTool, tool => (tool !== InkTool.None) && (this._controlButton = false));
+        reaction(() => this._controlButton, button => button && (CurrentUserUtils.ActiveTool = InkTool.None));
+        reaction(() => CurrentUserUtils.ActiveTool, tool => (tool !== InkTool.None) && (this._controlButton = false));
     }
 
     /**
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index 8ab54918c..487bbcd00 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -254,7 +254,7 @@ export class InkTranscription extends React.Component {
  */
 createInkGroup() {
     // TODO nda - if document being added to is a inkGrouping then we can just add to that group
-    if (CurrentUserUtils.SelectedTool === InkTool.Write) {
+    if (CurrentUserUtils.ActiveTool === InkTool.Write) {
         CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
             // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
             const selected = ffView.unprocessedDocs;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index aa5a815ac..35d89d2b1 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -345,7 +345,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
             onClick: (e: React.MouseEvent) => this._handledClick && e.stopPropagation(),
             onContextMenu: () => {
                 const cm = ContextMenu.Instance;
-                !Doc.UserDoc().noviceMode && cm?.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
+                !Doc.noviceMode && cm?.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
                 cm?.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" });
                 cm?.addItem({ description: "Edit Points", event: action(() => InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton), icon: "paint-brush" });
             }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7e3916b3f..791c64630 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -381,7 +381,7 @@ export class MainView extends React.Component {
                         addDocTab={this.addDocTabFunc}
                         pinToPres={emptyFunction}
                         docViewPath={returnEmptyDoclist}
-                        styleProvider={this._sidebarContent.proto === Doc.UserDoc().myDashboards || this._sidebarContent.proto === CurrentUserUtils.MyFilesystem ? DashboardStyleProvider : DefaultStyleProvider}
+                        styleProvider={this._sidebarContent.proto === CurrentUserUtils.MyDashboards || this._sidebarContent.proto === CurrentUserUtils.MyFilesystem ? DashboardStyleProvider : DefaultStyleProvider}
                         rootSelected={returnTrue}
                         removeDocument={returnFalse}
                         ScreenToLocalTransform={this.mainContainerXf}
@@ -656,7 +656,7 @@ export class MainView extends React.Component {
                             <GestureOverlay> {this.mainDashboardArea}   </GestureOverlay>
                         </>;
                     case "home":  return <DashboardView/>;
-                } })(StrCast(Doc.UserDoc().activePage))
+                } })(CurrentUserUtils.ActivePage)
             }
             
             <PreviewCursor />
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index bdb3bb857..598fff29a 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -4,7 +4,7 @@ import { observer } from "mobx-react";
 import { computedFn } from "mobx-utils";
 import * as React from "react";
 import ReactLoading from 'react-loading';
-import { Doc, WidthSym, HeightSym } from "../../fields/Doc";
+import { Doc, WidthSym, HeightSym, DocListCast } from "../../fields/Doc";
 import { Id } from "../../fields/FieldSymbols";
 import { Cast, NumCast } from "../../fields/Types";
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, Utils } from "../../Utils";
@@ -149,7 +149,7 @@ export class OverlayView extends React.Component {
     }
 
     removeOverlayDoc = (doc: Doc | Doc[]) => {
-        (doc instanceof Doc ? [doc] : doc).forEach(doc => Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc));
+        (doc instanceof Doc ? [doc] : doc).forEach(doc => Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, doc));
         return true;
     }
     
@@ -158,7 +158,7 @@ export class OverlayView extends React.Component {
     }.bind(this));
 
     @computed get overlayDocs() {
-        return CurrentUserUtils.MyOverlayDocs?.map(d => {
+        return DocListCast(CurrentUserUtils.MyOverlayDocs?.data).map(d => {
             let offsetx = 0, offsety = 0;
             const dref = React.createRef<HTMLDivElement>();
             const onPointerMove = action((e: PointerEvent, down: number[]) => {
@@ -172,7 +172,7 @@ export class OverlayView extends React.Component {
                     dragData.dropAction = "move";
                     dragData.removeDocument = (doc: Doc | Doc[]) => {
                         const docs = (doc instanceof Doc) ? [doc] : doc;
-                        docs.forEach(d => Doc.RemoveDocFromList(Cast(Doc.UserDoc().myOverlayDocs, Doc, null), "data", d));
+                        docs.forEach(d => Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, d));
                         return true;
                     };
                     dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 74055f057..9c6d9a108 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -200,7 +200,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                     {list}
                 </div>
             </div>
-            {Doc.UserDoc().noviceMode ? (null) : <div onPointerDown={this.editOnClickScript} className="onClickFlyout-editScript"> Edit onClick Script</div>}
+            {Doc.noviceMode ? (null) : <div onPointerDown={this.editOnClickScript} className="onClickFlyout-editScript"> Edit onClick Script</div>}
         </div>;
     }
     @computed
@@ -229,7 +229,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         const isTree = this.selectedDoc?._viewType === CollectionViewType.Tree;
         const isTabView = this.selectedTabView;
         const toggle = (ele: JSX.Element | null, style?: React.CSSProperties) => <div className="propertiesButtons-button" style={style}> {ele} </div>;
-        const isNovice = Doc.UserDoc().noviceMode;
+        const isNovice = Doc.noviceMode;
         return !this.selectedDoc ? (null) :
             <div className="propertiesButtons">
                 {toggle(this.titleButton)}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 2cf334ae1..90c86fa18 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -11,7 +11,7 @@ import { Id } from "../../fields/FieldSymbols";
 import { InkField } from "../../fields/InkField";
 import { List } from "../../fields/List";
 import { ComputedField } from "../../fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types";
+import { Cast, NumCast, StrCast, DocCast } from "../../fields/Types";
 import { denormalizeEmail, GetEffectiveAcl, SharingPermissions } from "../../fields/util";
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from "../../Utils";
 import { DocumentType } from "../documents/DocumentTypes";
@@ -340,7 +340,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
      */
     @undoBatch
     changePermissions = (e: any, user: string) => {
-        const docs = SelectionManager.Views().length < 2 ? [this.selectedDoc] : SelectionManager.Views().map(docView => docView.props.Document);
+        const docs = SelectionManager.Views().length < 2 ? (this.selectedDoc ? [this.selectedDoc]:[]) : SelectionManager.Views().map(docView => docView.props.Document);
         SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs);
     }
 
@@ -355,7 +355,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
             value={permission}
             onChange={e => this.changePermissions(e, user)}>
             {dropdownValues.filter(permission =>
-                !Doc.UserDoc().noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission =>
+                !Doc.noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission =>
                     <option key={permission} value={permission}>  {permission}  </option>)}
         </select>;
     }
@@ -419,7 +419,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
 
         // all selected docs
         const docs = SelectionManager.Views().length < 2 ?
-            [this.layoutDocAcls ? this.selectedDoc : this.selectedDoc[DataSym]]
+            [this.layoutDocAcls ? this.selectedDoc : this.selectedDoc?.[DataSym]]
             : SelectionManager.Views().map(docView => this.layoutDocAcls ? docView.props.Document : docView.props.Document[DataSym]);
 
         const target = docs[0];
@@ -907,7 +907,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
             {!this.openSharing ? (null) :
                 <div className="propertiesView-sharing-content">
                     <div className="propertiesView-buttonContainer">
-                        {!Doc.UserDoc().noviceMode ? (<div className="propertiesView-acls-checkbox">
+                        {!Doc.noviceMode ? (<div className="propertiesView-acls-checkbox">
                             <Checkbox
                                 color="primary"
                                 onChange={action(() => this.layoutDocAcls = !this.layoutDocAcls)}
@@ -931,42 +931,46 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
      * If it doesn't exist, it creates it.
      */
     checkFilterDoc() {
-        if (!this.selectedDoc.currentFilter) this.selectedDoc.currentFilter = CurrentUserUtils.createFilterDoc();
+        if (!this.selectedDoc?.currentFilter) this.selectedDoc!.currentFilter = CurrentUserUtils.createFilterDoc();
     }
 
     /**
      * Creates a new currentFilter for this.filterDoc, 
      */
     createNewFilterDoc = () => {
-        const currentDocFilters = this.selectedDoc._docFilters;
-        const currentDocRangeFilters = this.selectedDoc._docRangeFilters;
-        this.selectedDoc._docFilters = new List<string>();
-        this.selectedDoc._docRangeFilters = new List<string>();
-        (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters;
-        (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters;
-        this.selectedDoc.currentFilter = CurrentUserUtils.createFilterDoc();
+        if (this.selectedDoc) {
+            const currentDocFilters = this.selectedDoc._docFilters;
+            const currentDocRangeFilters = this.selectedDoc._docRangeFilters;
+            this.selectedDoc._docFilters = new List<string>();
+            this.selectedDoc._docRangeFilters = new List<string>();
+            (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters;
+            (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters;
+            this.selectedDoc.currentFilter = CurrentUserUtils.createFilterDoc();
+        }
     }
 
     /**
      * Updates this.filterDoc's currentFilter and saves the docFilters on the currentFilter
      */
     updateFilterDoc = (doc: Doc) => {
-        if (doc === this.selectedDoc.currentFilter) return; // causes problems if you try to reapply the same doc
-        const savedDocFilters = doc._docFiltersList;
-        const currentDocFilters = this.selectedDoc._docFilters;
-        this.selectedDoc._docFilters = new List<string>();
-        (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters;
-        this.selectedDoc.currentFilter = doc;
-        doc._docFiltersList = new List<string>();
-        this.selectedDoc._docFilters = savedDocFilters;
-
-        const savedDocRangeFilters = doc._docRangeFiltersList;
-        const currentDocRangeFilters = this.selectedDoc._docRangeFilters;
-        this.selectedDoc._docRangeFilters = new List<string>();
-        (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters;
-        this.selectedDoc.currentFilter = doc;
-        doc._docRangeFiltersList = new List<string>();
-        this.selectedDoc._docRangeFilters = savedDocRangeFilters;
+        if (this.selectedDoc) {
+            if (doc === this.selectedDoc.currentFilter) return; // causes problems if you try to reapply the same doc
+            const savedDocFilters = doc._docFiltersList;
+            const currentDocFilters = this.selectedDoc._docFilters;
+            this.selectedDoc._docFilters = new List<string>();
+            (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters;
+            this.selectedDoc.currentFilter = doc;
+            doc._docFiltersList = new List<string>();
+            this.selectedDoc._docFilters = savedDocFilters;
+
+            const savedDocRangeFilters = doc._docRangeFiltersList;
+            const currentDocRangeFilters = this.selectedDoc._docRangeFilters;
+            this.selectedDoc._docRangeFilters = new List<string>();
+            (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters;
+            this.selectedDoc.currentFilter = doc;
+            doc._docRangeFiltersList = new List<string>();
+            this.selectedDoc._docRangeFilters = savedDocRangeFilters;
+        }
     }
 
     @computed get filtersSubMenu() {
@@ -1059,13 +1063,13 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                     <FontAwesomeIcon icon={this.openFields ? "caret-down" : "caret-right"} size="lg" color="white" />
                 </div>
             </div>
-            {!Doc.UserDoc().noviceMode && this.openFields ? <div className="propertiesView-fields-checkbox">
+            {!Doc.noviceMode && this.openFields ? <div className="propertiesView-fields-checkbox">
                 {this.fieldsCheckbox}
                 <div className="propertiesView-fields-checkbox-text">Layout</div>
             </div> : null}
             {!this.openFields ? (null) :
                 <div className="propertiesView-fields-content">
-                    {Doc.UserDoc().noviceMode ? this.noviceFields : this.expandedField}
+                    {Doc.noviceMode ? this.noviceFields : this.expandedField}
                 </div>}
         </div>;
     }
@@ -1122,7 +1126,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
 
     @undoBatch
     handleDescriptionChange = action((value: string) => {
-        if (LinkManager.currentLink) {
+        if (LinkManager.currentLink && this.selectedDoc) {
             this.selectedDoc.description = value;
             this.description = value;
             return true;
@@ -1131,7 +1135,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
 
     @undoBatch
     handleLinkRelationshipChange = action((value: string) => {
-        if (LinkManager.currentLink) {
+        if (LinkManager.currentLink && this.selectedDoc) {
             this.selectedDoc.linkRelationship = value;
             this.relationship = value;
             return true;
@@ -1188,7 +1192,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
 
     @undoBatch
     changeFollowBehavior = action((follow: string) => {
-        if (LinkManager.currentLink) {
+        if (LinkManager.currentLink && this.selectedDoc) {
             this.selectedDoc.followLinkLocation = follow;
             return true;
         }
@@ -1219,18 +1223,18 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     }
 
     toggleAnchor = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc.linkAutoMove = !this.selectedDoc.linkAutoMove)));
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc.linkAutoMove = !this.selectedDoc.linkAutoMove))));
     }
 
     toggleArrow = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc.displayArrow = !this.selectedDoc.displayArrow)));
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc.displayArrow = !this.selectedDoc.displayArrow))));
     }
 
     toggleZoomToTarget1 = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => Cast(this.selectedDoc.anchor1, Doc, null).followLinkZoom = !Cast(this.selectedDoc.anchor1, Doc, null).followLinkZoom)));
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (DocCast(this.selectedDoc.anchor1).followLinkZoom = !DocCast(this.selectedDoc.anchor1).followLinkZoom))));
     }
     toggleZoomToTarget2 = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => Cast(this.selectedDoc.anchor2, Doc, null).followLinkZoom = !Cast(this.selectedDoc.anchor2, Doc, null).followLinkZoom)));
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (DocCast(this.selectedDoc.anchor2).followLinkZoom = !DocCast(this.selectedDoc.anchor2).followLinkZoom))));
     }
 
     @computed
@@ -1238,7 +1242,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         return <input
             autoComplete={"off"}
             id="link_relationship_input"
-            value={StrCast(this.selectedDoc.linkRelationship)}
+            value={StrCast(this.selectedDoc?.linkRelationship)}
             onKeyDown={this.onRelationshipKey}
             onBlur={this.onSelectOutRelationship}
             onChange={e => this.handleLinkRelationshipChange(e.currentTarget.value)}
@@ -1252,7 +1256,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         return <input
             autoComplete={"off"}
             id="link_description_input"
-            value={StrCast(this.selectedDoc.description)}
+            value={StrCast(this.selectedDoc?.description)}
             onKeyDown={this.onDescriptionKey}
             onBlur={this.onSelectOutDesc}
             onChange={e => this.handleDescriptionChange(e.currentTarget.value)}
@@ -1273,7 +1277,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     // }
 
     render() {
-        const isNovice = BoolCast(Doc.UserDoc().noviceMode);
+        const isNovice = Doc.noviceMode;
         if (!this.selectedDoc && !this.isPres) {
             return <div className="propertiesView" style={{ width: this.props.width }}>
                 <div className="propertiesView-title" style={{ width: this.props.width }}>
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 636f7042f..689ee4fc1 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -15,6 +15,7 @@ import { DocumentView } from "./nodes/DocumentView";
 import { DefaultStyleProvider } from "./StyleProvider";
 import './TemplateMenu.scss';
 import React = require("react");
+import { CurrentUserUtils } from "../util/CurrentUserUtils";
 
 @observer
 class TemplateToggle extends React.Component<{ template: string, checked: boolean, toggle: (event: React.ChangeEvent<HTMLInputElement>, template: string) => void }> {
@@ -111,22 +112,22 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         const firstDoc = this.props.docViews[0].props.Document;
         const templateName = StrCast(firstDoc.layoutKey, "layout").replace("layout_", "");
         const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null)?.data);
-        const addedTypes = Doc.UserDoc().noviceMode ? [] : DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data);
+        const addedTypes = Doc.noviceMode ? [] : DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data);
         const layout = Doc.Layout(firstDoc);
         const templateMenu: Array<JSX.Element> = [];
         this.props.templates?.forEach((checked, template) =>
             templateMenu.push(<TemplateToggle key={template} template={template} checked={checked} toggle={this.toggleTemplate} />));
         templateMenu.push(<OtherToggle key={"audio"} name={"Audio"} checked={firstDoc._showAudio ? true : false} toggle={this.toggleAudio} />);
         templateMenu.push(<OtherToggle key={"default"} name={"Default"} checked={templateName === "layout"} toggle={this.toggleDefault} />);
-        !Doc.UserDoc().noviceMode && templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={!layout._chromeHidden} toggle={this.toggleChrome} />);
+        !Doc.noviceMode && templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={!layout._chromeHidden} toggle={this.toggleChrome} />);
         addedTypes.concat(noteTypes).map(template => template.treeViewChecked = this.templateIsUsed(firstDoc, template));
         this._addedKeys && Array.from(this._addedKeys).filter(key => !noteTypes.some(nt => nt.title === key)).forEach(template => templateMenu.push(
             <OtherToggle key={template} name={template} checked={templateName === template} toggle={e => this.toggleLayout(e, template)} />));
         return <ul className="template-list" style={{ display: "block" }}>
-            {Doc.UserDoc().noviceMode ? (null) : <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress} />}
+            {Doc.noviceMode ? (null) : <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress} />}
             {templateMenu}
-            {Doc.UserDoc().noviceMode ? (null) : <CollectionTreeView
-                Document={Doc.UserDoc().templateDocs as Doc}
+            {Doc.noviceMode ? (null) : <CollectionTreeView
+                Document={CurrentUserUtils.MyTemplates}
                 CollectionView={undefined}
                 ContainingCollectionDoc={undefined}
                 ContainingCollectionView={undefined}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 1ebbe08ef..5f36a7a51 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -368,7 +368,7 @@ export class CollectionDockingView extends CollectionSubView() {
             }
         }
         if (!e.nativeEvent.cancelBubble && !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) &&
-            ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) {
+            ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) {
             e.stopPropagation();
         }
     }
@@ -433,8 +433,8 @@ export class CollectionDockingView extends CollectionSubView() {
 
     stackCreated = (stack: any) => {
         stack.header?.element.on('mousedown', (e: any) => {
-            if (e.target === stack.header?.element[0] && e.button === 2) {
-                const dashboard= CurrentUserUtils.ActiveDashboard;
+            const dashboard = CurrentUserUtils.ActiveDashboard;
+            if (dashboard && e.target === stack.header?.element[0] && e.button === 2) {
                 dashboard["pane-count"] = NumCast(dashboard["pane-count"]) + 1;
                 const docToAdd = Docs.Create.FreeformDocument([], {
                     _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _backgroundGridShow: true, _fitWidth: true, title: `Untitled Tab ${NumCast(dashboard["pane-count"])}`,
@@ -462,12 +462,14 @@ export class CollectionDockingView extends CollectionSubView() {
             .click(action(() => {
                 // stack.config.fixed = !stack.config.fixed;  // force the stack to have a fixed size
                 const dashboard = CurrentUserUtils.ActiveDashboard;
-                dashboard["pane-count"] = NumCast(dashboard["pane-count"]) + 1;
-                const docToAdd = Docs.Create.FreeformDocument([], {
-                    _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, _backgroundGridShow: true, title: `Untitled Tab ${NumCast(dashboard["pane-count"])}`
-                });
-                this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd);
-                CollectionDockingView.AddSplit(docToAdd, "", stack);
+                if (dashboard) {
+                    dashboard["pane-count"] = NumCast(dashboard["pane-count"]) + 1;
+                    const docToAdd = Docs.Create.FreeformDocument([], {
+                        _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, _backgroundGridShow: true, title: `Untitled Tab ${NumCast(dashboard["pane-count"])}`
+                    });
+                    this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd);
+                    CollectionDockingView.AddSplit(docToAdd, "", stack);
+                }
             }));
     }
 
@@ -480,6 +482,6 @@ ScriptingGlobals.add(function openInLightbox(doc: any) { LightboxView.AddDocTab(
     "opens up document in a lightbox", "(doc: any)");
 ScriptingGlobals.add(function openOnRight(doc: any) { return CollectionDockingView.AddSplit(doc, "right"); },
     "opens up document in tab on right side of the screen", "(doc: any)");
-ScriptingGlobals.add(function openInOverlay(doc: any) { return Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc); },
+ScriptingGlobals.add(function openInOverlay(doc: any) { return Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, doc); },
     "opens up document in screen overlay layer", "(doc: any)");
 ScriptingGlobals.add(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.ReplaceTab(doc, "right", undefined, shiftKey); });
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 1c25421f5..9b1bb5b97 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -268,17 +268,20 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
                  self.target._searchFilterDocs = compareLists(self['target-searchFilterDocs'],self.target._searchFilterDocs) ? undefined: copyField(self['target-searchFilterDocs']);`,
         immediate: undoBatch((source: Doc[]) => { this.target._docFilters = undefined; this.target._searchFilterDocs = undefined; }),
         initialize: (button: Doc) => {
-            button['target-docFilters'] = (CurrentUserUtils.MySearcher._docFilters || CurrentUserUtils.ActiveDashboard._docFilters) instanceof ObjectField ?
-                ObjectField.MakeCopy((CurrentUserUtils.MySearcher._docFilters || CurrentUserUtils.ActiveDashboard._docFilters) as any as ObjectField) : undefined;
-            button['target-searchFilterDocs'] = CurrentUserUtils.ActiveDashboard._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(CurrentUserUtils.ActiveDashboard._searchFilterDocs as any as ObjectField) : undefined;
+            const activeDash = CurrentUserUtils.ActiveDashboard;
+            if (activeDash) {
+                button['target-docFilters'] = (CurrentUserUtils.MySearcher._docFilters || activeDash._docFilters) instanceof ObjectField ?
+                    ObjectField.MakeCopy((CurrentUserUtils.MySearcher._docFilters || activeDash._docFilters) as any as ObjectField) : undefined;
+                button['target-searchFilterDocs'] = activeDash._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(activeDash._searchFilterDocs as any as ObjectField) : undefined;
+            }
         },
     };
 
-    @computed get _freeform_commands() { return Doc.UserDoc().noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; }
-    @computed get _stacking_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; }
-    @computed get _masonry_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; }
-    @computed get _schema_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._templateCommand, this._narrativeCommand]; }
-    @computed get _doc_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand]; }
+    @computed get _freeform_commands() { return Doc.noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; }
+    @computed get _stacking_commands() { return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand]; }
+    @computed get _masonry_commands() { return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand]; }
+    @computed get _schema_commands() { return Doc.noviceMode ? undefined : [this._templateCommand, this._narrativeCommand]; }
+    @computed get _doc_commands() { return Doc.noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand]; }
     @computed get _tree_commands() { return undefined; }
     private get _buttonizableCommands() {
         switch (this.props.type) {
@@ -467,7 +470,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
     @action
     startRecording = () => {
         const doc = Docs.Create.ScreenshotDocument({ title: "screen recording", _fitWidth: true, _width: 400, _height: 200, mediaState: "pendingRecording" });
-        //Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc);
+        //Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, doc);
         CollectionDockingView.AddSplit(doc, "right");
     }
 
@@ -691,8 +694,8 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
     @action clearKeepPrimitiveMode() { this._selectedPrimitive = this._shapePrims.length; }
     @action primCreated() {
         if (!this._keepPrimitiveMode) { //get out of ink mode after each stroke=
-            if (CurrentUserUtils.SelectedTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor);
-            CurrentUserUtils.SelectedTool = InkTool.None;
+            if (CurrentUserUtils.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor);
+            CurrentUserUtils.ActiveTool = InkTool.None;
             this._selectedPrimitive = this._shapePrims.length;
             SetActiveArrowStart("none");
             SetActiveArrowEnd("none");
@@ -734,11 +737,11 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
             if (this._selectedPrimitive !== i) {
                 this._selectedPrimitive = i;
                 if (this._title[i] === "highlighter") {
-                    CurrentUserUtils.SelectedTool = InkTool.Highlighter;
+                    CurrentUserUtils.ActiveTool = InkTool.Highlighter;
                     GestureOverlay.Instance.SavedColor = ActiveInkColor();
                     SetActiveInkColor("rgba(245, 230, 95, 0.75)");
                 } else {
-                    CurrentUserUtils.SelectedTool = InkTool.Pen;
+                    CurrentUserUtils.ActiveTool = InkTool.Pen;
                 }
                 SetActiveArrowStart(this._head[i]);
                 SetActiveArrowEnd(this._end[i]);
@@ -747,7 +750,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
                 GestureOverlay.Instance.InkShape = this._shapePrims[i];
             } else {
                 this._selectedPrimitive = this._shapePrims.length;
-                CurrentUserUtils.SelectedTool = InkTool.None;
+                CurrentUserUtils.ActiveTool = InkTool.None;
                 SetActiveArrowStart("");
                 SetActiveArrowEnd("");
                 GestureOverlay.Instance.InkShape = "";
@@ -840,7 +843,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
                         {this.widthPicker}
                         {this.colorPicker}
                         {this.fillPicker}
-                        {Doc.UserDoc().noviceMode || this.props.isDoc ? (null) :
+                        {Doc.noviceMode || this.props.isDoc ? (null) :
                             <>
                                 <Tooltip key="back" title={<div className="dash-tooltip">Back Frame</div>} placement="bottom">
                                     <div className="backKeyframe" onClick={this.prevKeyframe}>
@@ -879,7 +882,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
         const val = value.toLowerCase();
         const docs = DocListCast(this.document[this.props.fieldKey]);
 
-        if (Doc.UserDoc().noviceMode) {
+        if (Doc.noviceMode) {
             if (docs instanceof Doc) {
                 const keys = Object.keys(docs).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 ||
                     key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 ||
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index dddae4a34..277fcd59c 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -598,7 +598,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             <>
                 {buttonMenu || noviceExplainer ? <div className="documentButtonMenu">
                     {buttonMenu ? this.buttonMenu : null}
-                    {Doc.UserDoc().noviceMode && noviceExplainer ?
+                    {Doc.noviceMode && noviceExplainer ?
                         <div className="documentExplanation">
                             {noviceExplainer}
                         </div>
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 7f96217b8..f3a798143 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -224,8 +224,8 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                     }
                 }, icon: "compress-arrows-alt"
             }));
-        !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: "Doc Fields ...", subitems: docItems, icon: "eye" });
-        !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: "Containers ...", subitems: layoutItems, icon: "eye" });
+        !Doc.noviceMode && ContextMenu.Instance.addItem({ description: "Doc Fields ...", subitems: docItems, icon: "eye" });
+        !Doc.noviceMode && ContextMenu.Instance.addItem({ description: "Containers ...", subitems: layoutItems, icon: "eye" });
         ContextMenu.Instance.setDefaultItem("::", (name: string): void => {
             Doc.GetProto(this.props.Document)[name] = "";
             const created = Docs.Create.TextDocument("", { title: name, _width: 250, _autoHeight: true });
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index e809bfbce..ba72fb7b9 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -67,7 +67,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     @computed get treeChildren() { TraceMobx(); return this.props.childDocuments || this.childDocs; }
     @computed get outlineMode() { return this.doc.treeViewType === TreeViewType.outline; }
     @computed get fileSysMode() { return this.doc.treeViewType === TreeViewType.fileSystem; }
-    @computed get dashboardMode() { return this.doc === Doc.UserDoc().myDashboards; }
+    @computed get dashboardMode() { return this.doc === CurrentUserUtils.MyDashboards; }
 
     @observable _explainerHeight = 0; // height of the description of the tree view
 
@@ -76,7 +76,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     // these should stay in synch with counterparts in DocComponent.ts ViewBoxAnnotatableComponent
     @observable _isAnyChildContentActive = false;
     whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive));
-    isContentActive = (outsideReaction?: boolean) => (CurrentUserUtils.SelectedTool !== InkTool.None ||
+    isContentActive = (outsideReaction?: boolean) => (CurrentUserUtils.ActiveTool !== InkTool.None ||
         (this.props.isContentActive?.() || this.props.Document.forceActive ||
             this.props.isSelected(outsideReaction) || this._isAnyChildContentActive ||
             this.props.rootSelected(outsideReaction)) ? true : false)
@@ -164,7 +164,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     }
     onContextMenu = (e: React.MouseEvent): void => {
         // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
-        if (!Doc.UserDoc().noviceMode) {
+        if (!Doc.noviceMode) {
             const layoutItems: ContextMenuProps[] = [];
             layoutItems.push({ description: "Make tree state " + (this.doc.treeViewOpenIsTransient ? "persistent" : "transient"), event: () => this.doc.treeViewOpenIsTransient = !this.doc.treeViewOpenIsTransient, icon: "paint-brush" });
             layoutItems.push({ description: (this.doc.treeViewHideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.doc.treeViewHideHeaderFields = !this.doc.treeViewHideHeaderFields, icon: "paint-brush" });
@@ -286,7 +286,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     }
 
     @computed get noviceExplainer() {
-        return !Doc.UserDoc().noviceMode || !this.rootDoc.explainer ? (null) :
+        return !Doc.noviceMode || !this.rootDoc.explainer ? (null) :
             <div className="documentExplanation"> {this.rootDoc.explainer} </div>;
     }
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 965f0a352..4b5c5e3fb 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -160,8 +160,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
         subItems.push({ description: "Masonry", event: () => func(CollectionViewType.Masonry), icon: "columns" });
         subItems.push({ description: "Carousel", event: () => func(CollectionViewType.Carousel), icon: "columns" });
         subItems.push({ description: "3D Carousel", event: () => func(CollectionViewType.Carousel3D), icon: "columns" });
-        !Doc.UserDoc().noviceMode && subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" });
-        !Doc.UserDoc().noviceMode && subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" });
+        !Doc.noviceMode && subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" });
+        !Doc.noviceMode && subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" });
         subItems.push({ description: "Grid", event: () => func(CollectionViewType.Grid), icon: "th-list" });
 
         if (!Doc.IsSystem(this.rootDoc) && !this.rootDoc.isGroup && !this.rootDoc.annotationOn) {
@@ -184,16 +184,16 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
 
             const options = cm.findByDescription("Options...");
             const optionItems = options && "subitems" in options ? options.subitems : [];
-            !Doc.UserDoc().noviceMode ? optionItems.splice(0, 0, { description: `${this.rootDoc.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.rootDoc.forceActive = !this.rootDoc.forceActive, icon: "project-diagram" }) : null;
+            !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.rootDoc.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.rootDoc.forceActive = !this.rootDoc.forceActive, icon: "project-diagram" }) : null;
             if (this.rootDoc.childLayout instanceof Doc) {
                 optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, "add:right"), icon: "project-diagram" });
             }
             if (this.rootDoc.childClickedOpenTemplateView instanceof Doc) {
                 optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, "add:right"), icon: "project-diagram" });
             }
-            !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer, icon: "project-diagram" });
+            !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer, icon: "project-diagram" });
 
-            if (!Doc.UserDoc().noviceMode) {
+            if (!Doc.noviceMode) {
                 optionItems.push({
                     description: "Create Branch", event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), "add:right"), icon: "project-diagram"
                 });
@@ -210,7 +210,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
 
             !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "hand-point-right" });
 
-            if (!Doc.UserDoc().noviceMode && !this.rootDoc.annotationOn) {
+            if (!Doc.noviceMode && !this.rootDoc.annotationOn) {
                 const existingOnClick = cm.findByDescription("OnClick...");
                 const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
                 const funcs = [{ key: "onChildClick", name: "On Child Clicked" }, { key: "onChildDoubleClick", name: "On Child Double Clicked" }];
@@ -230,7 +230,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
                 !Doc.IsSystem(this.rootDoc) && !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" });
             }
 
-            if (!Doc.UserDoc().noviceMode) {
+            if (!Doc.noviceMode) {
                 const more = cm.findByDescription("More...");
                 const moreItems = more && "subitems" in more ? more.subitems : [];
                 moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.rootDoc) });
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 022b9fa24..70db121d1 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -277,7 +277,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array
         });
         if (!Array.from(CollectionDockingView.Instance.tabMap).map(d => d.DashDoc).includes(curPres)) {
-            const docs = Cast(Cast(Doc.UserDoc().myOverlayDocs, Doc, null).data, listSpec(Doc), []);
+            const docs = Cast(CurrentUserUtils.MyOverlayDocs.data, listSpec(Doc), []);
             if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1);
             CollectionDockingView.AddSplit(curPres, "right");
             setTimeout(() => DocumentManager.Instance.jumpToDocument(docList.lastElement(), false, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 59dc5671b..704b8989a 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -114,7 +114,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         return this.doc.viewType === CollectionViewType.Docking ? this.fieldKey :
             this.props.treeView.dashboardMode ? this.fieldKey :
                 this.props.treeView.fileSysMode ? (this.doc.isFolder ? this.fieldKey : "aliases") : // for displaying 
-                    this.props.treeView.outlineMode || this.childDocs ? this.fieldKey : Doc.UserDoc().noviceMode ? "layout" : StrCast(this.props.treeView.doc.treeViewExpandedView, "fields");
+                    this.props.treeView.outlineMode || this.childDocs ? this.fieldKey : Doc.noviceMode ? "layout" : StrCast(this.props.treeView.doc.treeViewExpandedView, "fields");
     }
 
     @computed get doc() { return this.props.document; }
@@ -560,8 +560,8 @@ export class TreeView extends React.Component<TreeViewProps> {
         const links = () => DocListCast(this.doc.links).length && !this.props.treeView.dashboardMode ? "links" : "";
         const data = () => this.childDocs || this.props.treeView.dashboardMode ? this.fieldKey : "";
         const aliases = () => this.props.treeView.dashboardMode ? "" : "aliases";
-        const fields = () => Doc.UserDoc().noviceMode ? "" : "fields";
-        const layout = (Doc.UserDoc().noviceMode) || this.doc.viewType === CollectionViewType.Docking ? [] : ["layout"];
+        const fields = () => Doc.noviceMode ? "" : "fields";
+        const layout = (Doc.noviceMode) || this.doc.viewType === CollectionViewType.Docking ? [] : ["layout"];
         return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [aliases(), links(), annos()] : []), fields()].filter(m => m);
     }
     @action
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c07e44fcc..ffe146ae4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -458,7 +458,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 !this.props.Document._isGroup && // group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag
                 !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) &&
                 !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
-                switch (CurrentUserUtils.SelectedTool) {
+                switch (CurrentUserUtils.ActiveTool) {
                     case InkTool.Highlighter:
                         break;
                     // TODO: nda - this where we want to create the new "writingDoc" collection that we add strokes to
@@ -497,7 +497,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     this.addMoveListeners();
                     this.removeEndListeners();
                     this.addEndListeners();
-                    if (CurrentUserUtils.SelectedTool === InkTool.None) {
+                    if (CurrentUserUtils.ActiveTool === InkTool.None) {
                         this._lastX = pt.pageX;
                         this._lastY = pt.pageY;
                         e.preventDefault();
@@ -519,9 +519,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             case GestureUtils.Gestures.Stroke:
                 const points = ge.points;
                 const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
-                const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), points,
+                const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), points,
                     { title: "ink stroke", x: B.x - ActiveInkWidth() / 2, y: B.y - ActiveInkWidth() / 2, _width: B.width + ActiveInkWidth(), _height: B.height + ActiveInkWidth() });
-                if (CurrentUserUtils.SelectedTool === InkTool.Write) {
+                if (CurrentUserUtils.ActiveTool === InkTool.Write) {
                     this.unprocessedDocs.push(inkDoc);
                     CollectionFreeFormView.collectionsWithUnprocessedInk.add(this);
                 }
@@ -711,7 +711,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     onPointerMove = (e: PointerEvent): void => {
         if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return;
         if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
-            Doc.UserDoc().activeInkTool = InkTool.None;
+            CurrentUserUtils.ActiveTool = InkTool.None;
             if (this.props.isContentActive(true)) e.stopPropagation();
         } else if (!e.cancelBubble) {
             if (this.tryDragCluster(e, this._hitCluster)) {
@@ -834,7 +834,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (!e.cancelBubble) {
             const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
             if (myTouches[0]) {
-                if (CurrentUserUtils.SelectedTool === InkTool.None) {
+                if (CurrentUserUtils.ActiveTool === InkTool.None) {
                     if (this.tryDragCluster(e, this._hitCluster)) {
                         e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
                         e.preventDefault();
@@ -985,7 +985,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     onPointerWheel = (e: React.WheelEvent): void => {
-        if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || CurrentUserUtils.MyOverlayDocs.includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
+        if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
         if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
             e.stopPropagation();
         }
@@ -1031,7 +1031,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 else if (ranges.yrange.max <= (panY - panelDim[1] / 2)) panY = ranges.yrange.min - panelDim[1] / 2;
             }
         }
-        if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || CurrentUserUtils.MyOverlayDocs.includes(this.Document)) {
+        if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.Document)) {
             this._viewTransition = panTime;
             const scale = this.getLocalTransform().inverse().Scale;
             const minScale = NumCast(this.rootDoc._viewScaleMin, 1);
@@ -1629,7 +1629,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const appearance = ContextMenu.Instance.findByDescription("Appearance...");
         const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
         appearanceItems.push({ description: "Reset View", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
-        !Doc.UserDoc().noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
+        !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
         appearanceItems.push({ description: `${this.fitContentsToBox ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitContentsToBox = !this.fitContentsToBox, icon: !this.fitContentsToBox ? "expand-arrows-alt" : "compress-arrows-alt" });
         appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, {pinDocView:true, panelWidth: this.props.PanelWidth(), panelHeight:this.props.PanelHeight()}), icon: "map-pin" });
         //appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" });
@@ -1640,27 +1640,27 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
         // this.props.Document._isGroup && this.childDocs.filter(s => s.type === DocumentType.INK).length > 0 && appearanceItems.push({ description: "Ink to math", event: () => this.transcribeStrokes(true), icon: "square-root-alt" });
 
-        !Doc.UserDoc().noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null;
+        !Doc.noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null;
         !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
 
         const viewctrls = ContextMenu.Instance.findByDescription("UI Controls...");
         const viewCtrlItems = viewctrls && "subitems" in viewctrls ? viewctrls.subitems : [];
-        !Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " Snap Lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" }) : null;
-        !Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document._useClusters), icon: "braille" }) : null;
+        !Doc.noviceMode ? viewCtrlItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " Snap Lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" }) : null;
+        !Doc.noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document._useClusters), icon: "braille" }) : null;
         !viewctrls && ContextMenu.Instance.addItem({ description: "UI Controls...", subitems: viewCtrlItems, icon: "eye" });
 
         const options = ContextMenu.Instance.findByDescription("Options...");
         const optionItems = options && "subitems" in options ? options.subitems : [];
-        !this.props.isAnnotationOverlay && !Doc.UserDoc().noviceMode &&
+        !this.props.isAnnotationOverlay && !Doc.noviceMode &&
             optionItems.push({ description: (this._showAnimTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this._showAnimTimeline = !this._showAnimTimeline), icon: "eye" });
         this.props.renderDepth && optionItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
-        if (!Doc.UserDoc().noviceMode) {
+        if (!Doc.noviceMode) {
             optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
         }
         !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
         const mores = ContextMenu.Instance.findByDescription("More...");
         const moreItems = mores && "subitems" in mores ? mores.subitems : [];
-        if (!Doc.UserDoc().noviceMode) {
+        if (!Doc.noviceMode) {
             moreItems.push({ description: "Export collection", icon: "download", event: async () => Doc.Zip(this.props.Document) });
             moreItems.push({ description: "Import exported collection", icon: "upload", event: ({ x, y }) => this.importDocument(x, y) });
         }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 051da795f..b62020a04 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -330,7 +330,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     onClick = (e: React.MouseEvent): void => {
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
             Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
-            if (CurrentUserUtils.SelectedTool === InkTool.None) {
+            if (CurrentUserUtils.ActiveTool === InkTool.None) {
                 if (!(e.nativeEvent as any).marqueeHit) {
                     (e.nativeEvent as any).marqueeHit = true;
                     if (!this.props.trySelectCluster(e.shiftKey)) {
@@ -367,7 +367,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         const newCollection = creator ? creator(selected, { title: "nested stack", }) : ((doc: Doc) => {
             Doc.GetProto(doc).data = new List<Doc>(selected);
             Doc.GetProto(doc).title = makeGroup ? "grouping" : "nested freeform";
-            !this.props.isAnnotationOverlay && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", Doc.GetProto(doc));
+            !this.props.isAnnotationOverlay && Doc.AddDocToList(CurrentUserUtils.MyFileOrphans, undefined, Doc.GetProto(doc));
             doc._panX = doc._panY = 0;
             return doc;
         })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
@@ -643,7 +643,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         return <div className="marqueeView"
             style={{
                 overflow: StrCast(this.props.Document._overflow),
-                cursor: [InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool) || this._visible || PresBox.startMarquee ? "crosshair" : "pointer"
+                cursor: [InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool) || this._visible || PresBox.startMarquee ? "crosshair" : "pointer"
             }}
 
             onDragOver={e => e.preventDefault()}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
index 0875c80b3..9653f2808 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
@@ -321,7 +321,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
         const whitelistKeys = ["context", "author", "*lastModified", "text", "data", "tags", "creationDate"];
         const keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
         const showKeys = new Set<string>();
-        [...keyOptions, ...whitelistKeys].forEach(key => (!Doc.UserDoc().noviceMode ||
+        [...keyOptions, ...whitelistKeys].forEach(key => (!Doc.noviceMode ||
             whitelistKeys.includes(key)
             || ((!key.startsWith("_") && key[0] === key[0].toUpperCase()) || key[0] === "#")) ? showKeys.add(key) : null);
         return Array.from(showKeys.keys()).filter(key => !this._searchTerm || key.includes(this._searchTerm));
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 669622455..8e24fce11 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -348,11 +348,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             Doc.GetProto(newDoc).recordingSource = this.dataDoc;
             Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.fieldKey}-recordingStart"]`);
             Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction("self.recordingSource.mediaState");
-            const overlayDoc = Doc.UserDoc().myOverlayDocs as Doc;
-            if (DocListCast(overlayDoc[Doc.LayoutFieldKey(overlayDoc)]).includes(this.rootDoc)) {
+            if (DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.rootDoc)) {
                 newDoc.x = this.rootDoc.x;
                 newDoc.y = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height);
-                Doc.AddDocToList(overlayDoc, undefined, newDoc);
+                Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, newDoc);
             } else {
                 this.props.addDocument?.(newDoc);
             }
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index d975baf9b..6d05fca5e 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -22,7 +22,6 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @undoBatch
     @action
     static switchColor(color: ColorState) {
-        // Doc.UserDoc().backgroundColor = Utils.colorString(color); // bcz: this can't go here ... needs a proper home in the settings panel
         SetActiveInkColor(color.hex);
 
         SelectionManager.Views().map(view => {
@@ -49,7 +48,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
             style={{ transform: `scale(${scaling})`, width: `${100 * scaling}%`, height: `${100 * scaling}%` }} >
 
             <SketchPicker
-                onChange={c => CurrentUserUtils.SelectedTool === InkTool.None && ColorBox.switchColor(c)}
+                onChange={c => CurrentUserUtils.ActiveTool === InkTool.None && ColorBox.switchColor(c)}
                 color={StrCast(SelectionManager.Views()?.[0]?.rootDoc?._backgroundColor, ActiveInkColor())}
                 presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986',
                     '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e2b37d2b4..4d84a8ad2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -327,7 +327,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             this._downX = touch.clientX;
             this._downY = touch.clientY;
             if (!e.nativeEvent.cancelBubble) {
-                if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc._lockedPosition && !CurrentUserUtils.MyOverlayDocs.includes(this.layoutDoc)) e.stopPropagation();
+                if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc._lockedPosition && !DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc)) e.stopPropagation();
                 this.removeMoveListeners();
                 this.addMoveListeners();
                 this.removeEndListeners();
@@ -341,7 +341,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (e.cancelBubble && this.props.isDocumentActive?.()) {
             this.removeMoveListeners();
         }
-        else if (!e.cancelBubble && (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.MyOverlayDocs.includes(this.layoutDoc)) {
+        else if (!e.cancelBubble && (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc)) {
             const touch = me.touchEvent.changedTouches.item(0);
             if (touch && (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3)) {
                 if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler)) {
@@ -543,9 +543,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     });
 
     onPointerDown = (e: React.PointerEvent): void => {
-        if (this.rootDoc.type === DocumentType.INK && CurrentUserUtils.SelectedTool === InkTool.Eraser) return;
+        if (this.rootDoc.type === DocumentType.INK && CurrentUserUtils.ActiveTool === InkTool.Eraser) return;
         // continue if the event hasn't been canceled AND we are using a mouse or this has an onClick or onDragStart function (meaning it is a button document)
-        if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool))) {
+        if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool))) {
             if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
                 e.stopPropagation();
                 if (SelectionManager.IsSelected(this.props.DocumentView(), true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
@@ -563,7 +563,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 !this.Document.ignoreClick &&
                 !e.ctrlKey &&
                 (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) &&
-                !CurrentUserUtils.MyOverlayDocs.includes(this.layoutDoc)) {
+                !DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc)) {
                 e.stopPropagation();
                 // don't preventDefault anymore.  Goldenlayout, PDF text selection and RTF text selection all need it to go though
                 //if (this.props.isSelected(true) && this.rootDoc.type !== DocumentType.PDF && this.layoutDoc._viewType !== CollectionViewType.Docking) e.preventDefault();
@@ -579,9 +579,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     onPointerMove = (e: PointerEvent): void => {
         if (e.cancelBubble) return;
-        if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool))) return;
+        if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool))) return;
 
-        if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.MyOverlayDocs.includes(this.layoutDoc)) {
+        if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc)) {
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
                 if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
                     document.removeEventListener("pointermove", this.onPointerMove);
@@ -697,7 +697,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     @action
     onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => {
-        if (e && this.rootDoc._hideContextMenu && Doc.UserDoc().noviceMode) {
+        if (e && this.rootDoc._hideContextMenu && Doc.noviceMode) {
             e.preventDefault();
             e.stopPropagation();
             //!this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false);
@@ -747,8 +747,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
             const appearance = cm.findByDescription("UI Controls...");
             const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : [];
-            !Doc.UserDoc().noviceMode && templateDoc && appearanceItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" });
-            !Doc.UserDoc().noviceMode && appearanceItems.push({
+            !Doc.noviceMode && templateDoc && appearanceItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" });
+            !Doc.noviceMode && appearanceItems.push({
                 description: "Add a Field", event: () => {
                     const alias = Doc.MakeAlias(this.rootDoc);
                     alias.layout = FormattedTextBox.LayoutString("newfield");
@@ -765,7 +765,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             !appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" });
 
             if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) {
-                !Doc.UserDoc().noviceMode && appearanceItems.splice(0, 0, { description: `${!this.layoutDoc._showAudio ? "Show" : "Hide"} Audio Button`, event: action(() => this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: "microphone" });
+                !Doc.noviceMode && appearanceItems.splice(0, 0, { description: `${!this.layoutDoc._showAudio ? "Show" : "Hide"} Audio Button`, event: action(() => this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: "microphone" });
                 const existingOnClick = cm.findByDescription("OnClick...");
                 const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
 
@@ -778,8 +778,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 }
                 !zorders && cm.addItem({ description: "ZOrder...", noexpand: true, subitems: zorderItems, icon: "compass" });
 
-                !Doc.UserDoc().noviceMode && onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
-                !Doc.UserDoc().noviceMode && onClicks.push({ description: "Toggle Detail", event: this.setToggleDetail, icon: "concierge-bell" });
+                !Doc.noviceMode && onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
+                !Doc.noviceMode && onClicks.push({ description: "Toggle Detail", event: this.setToggleDetail, icon: "concierge-bell" });
                 this.props.CollectionFreeFormDocumentView && onClicks.push({ description: (this.Document.followLinkZoom ? "Don't" : "") + " zoom following link", event: () => this.Document.followLinkZoom = !this.Document.followLinkZoom, icon: this.Document.ignoreClick ? "unlock" : "lock" });
 
                 if (!this.Document.annotationOn) {
@@ -803,7 +803,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             }
 
             const funcs: ContextMenuProps[] = [];
-            if (!Doc.UserDoc().noviceMode && this.layoutDoc.onDragStart) {
+            if (!Doc.noviceMode && this.layoutDoc.onDragStart) {
                 funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
                 funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
                 funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined });
@@ -813,8 +813,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             const more = cm.findByDescription("More...");
             const moreItems = more && "subitems" in more ? more.subitems : [];
             if (!Doc.IsSystem(this.rootDoc)) {
-                (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.UserDoc().noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: "users" });
-                if (!Doc.UserDoc().noviceMode) {
+                (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: "users" });
+                if (!Doc.noviceMode) {
                     moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
                     moreItems.push({ description: `${this.Document._chromeHidden ? "Show" : "Hide"} Chrome`, event: () => this.Document._chromeHidden = !this.Document._chromeHidden, icon: "project-diagram" });
 
@@ -835,9 +835,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             const help = cm.findByDescription("Help...");
             const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
             helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" });
-            !Doc.UserDoc().noviceMode && helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" });
-            !Doc.UserDoc().noviceMode && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
-            !Doc.UserDoc().noviceMode && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" });
+            !Doc.noviceMode && helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" });
+            !Doc.noviceMode && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
+            !Doc.noviceMode && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" });
             cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
         }
 
@@ -860,7 +860,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view);
     isContentActive = (outsideReaction?: boolean) => {
         return this.props.isContentActive() === false ? false : (
-            CurrentUserUtils.SelectedTool !== InkTool.None ||
+            CurrentUserUtils.ActiveTool !== InkTool.None ||
             SnappingManager.GetIsDragging() ||
             this.rootSelected() ||
             this.props.Document.forceActive ||
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index 5add09653..17b57cb3b 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -8,7 +8,7 @@ import { List } from "../../../fields/List";
 import { RichTextField } from "../../../fields/RichTextField";
 import { listSpec } from "../../../fields/Schema";
 import { ComputedField, ScriptField } from "../../../fields/ScriptField";
-import { Cast, NumCast, StrCast } from "../../../fields/Types";
+import { Cast, NumCast, StrCast, DocCast } from "../../../fields/Types";
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
 import { DocumentType } from "../../documents/DocumentTypes";
@@ -84,7 +84,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return "data";
     }
     @computed static get targetDocChildren() {
-        return DocListCast(FilterBox.targetDoc?.[FilterBox.targetDocChildKey] || CurrentUserUtils.ActiveDashboard.data);
+        return DocListCast(FilterBox.targetDoc?.[FilterBox.targetDocChildKey] || CurrentUserUtils.ActiveDashboard?.data);
     }
 
     @observable _loaded = false;
@@ -115,7 +115,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
         const keys = new Set<string>(noviceFields);
         this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key)));
-        return Array.from(keys.keys()).filter(key => key[0]).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_")) || noviceFields.includes(key) || !Doc.UserDoc().noviceMode).sort();
+        return Array.from(keys.keys()).filter(key => key[0]).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_")) || noviceFields.includes(key) || !Doc.noviceMode).sort();
     }
 
 
@@ -167,23 +167,25 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
     removeFilterDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).map(doc => this.removeFilter(StrCast(doc.title))).length ? true : false;
     public removeFilter = (filterName: string) => {
         const targetDoc = FilterBox.targetDoc;
-        const filterDoc = targetDoc.currentFilter as Doc;
-        const attributes = DocListCast(filterDoc.data);
-        const found = attributes.findIndex(doc => doc.title === filterName);
-        if (found !== -1) {
-            (filterDoc.data as List<Doc>).splice(found, 1);
-            const docFilter = Cast(targetDoc._docFilters, listSpec("string"));
-            if (docFilter) {
-                let index: number;
-                while ((index = docFilter.findIndex(item => item.split(":")[0] === filterName)) !== -1) {
-                    docFilter.splice(index, 1);
+        if (targetDoc) {
+            const filterDoc = targetDoc.currentFilter as Doc;
+            const attributes = DocListCast(filterDoc.data);
+            const found = attributes.findIndex(doc => doc.title === filterName);
+            if (found !== -1) {
+                (filterDoc.data as List<Doc>).splice(found, 1);
+                const docFilter = Cast(targetDoc._docFilters, listSpec("string"));
+                if (docFilter) {
+                    let index: number;
+                    while ((index = docFilter.findIndex(item => item.split(":")[0] === filterName)) !== -1) {
+                        docFilter.splice(index, 1);
+                    }
                 }
-            }
-            const docRangeFilters = Cast(targetDoc._docRangeFilters, listSpec("string"));
-            if (docRangeFilters) {
-                let index: number;
-                while ((index = docRangeFilters.findIndex(item => item.split(":")[0] === filterName)) !== -1) {
-                    docRangeFilters.splice(index, 3);
+                const docRangeFilters = Cast(targetDoc._docRangeFilters, listSpec("string"));
+                if (docRangeFilters) {
+                    let index: number;
+                    while ((index = docRangeFilters.findIndex(item => item.split(":")[0] === filterName)) !== -1) {
+                        docRangeFilters.splice(index, 3);
+                    }
                 }
             }
         }
@@ -194,6 +196,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
      */
     facetClick = (facetHeader: string) => {
         const { targetDoc, targetDocChildren } = FilterBox;
+        if (!targetDoc) return;
         const found = this.activeAttributes.findIndex(doc => doc.title === facetHeader);
         if (found !== -1) {
             this.removeFilter(facetHeader);
@@ -267,7 +270,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
      */
     @action
     changeBool = (e: any) => {
-        (FilterBox.targetDoc.currentFilter as Doc).filterBoolean = e.currentTarget.value;
+        FilterBox.targetDoc && (DocCast(FilterBox.targetDoc.currentFilter).filterBoolean = e.currentTarget.value);
     }
 
     /**
@@ -322,7 +325,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
      * Changes the title of the filterDoc
      */
     onTitleValueChange = (val: string) => {
-        this.props.Document.title = val || `FilterDoc for ${FilterBox.targetDoc.title}`;
+        this.props.Document.title = val || `FilterDoc for ${FilterBox.targetDoc?.title}`;
         return true;
     }
 
@@ -373,7 +376,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
             </div>
 
             <div className="filterBox-select-bool">
-                <select className="filterBox-selection" onChange={this.changeBool} defaultValue={StrCast((FilterBox.targetDoc.currentFilter as Doc)?.filterBoolean)}>
+                <select className="filterBox-selection" onChange={this.changeBool} defaultValue={StrCast((FilterBox.targetDoc?.currentFilter as Doc)?.filterBoolean)}>
                     <option value="AND" key="AND">AND</option>
                     <option value="OR" key="OR">OR</option>
                 </select>
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 8c27b3508..60eb48114 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -179,7 +179,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             funcs.push({ description: "Rotate Clockwise 90", event: this.rotate, icon: "expand-arrows-alt" });
             funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? "Dynamic Res" : "Full Res"}`, event: this.resolution, icon: "expand-arrows-alt" });
             funcs.push({ description: "Copy path", event: () => Utils.CopyText(this.choosePath(field.url)), icon: "expand-arrows-alt" });
-            if (!Doc.UserDoc().noviceMode) {
+            if (!Doc.noviceMode) {
                 funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
 
                 const existingAnalyze = ContextMenu.Instance?.findByDescription("Analyzers...");
@@ -357,7 +357,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         return <div className="imageBox-annotationLayer" style={{ height: this.props.PanelHeight() }} ref={this._annotationLayer} />;
     }
     marqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && NumCast(this.rootDoc._viewScale,1) <= NumCast(this.rootDoc.viewScaleMin,1) && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) {
+        if (!e.altKey && e.button === 0 && NumCast(this.rootDoc._viewScale,1) <= NumCast(this.rootDoc.viewScaleMin,1) && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) {
             setupMoveUpEvents(this, e, action(e => {
                 MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
                 this._marqueeing = [e.clientX, e.clientY];
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index b0b050cea..b58a9affb 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -52,7 +52,7 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro
     get paramsDoc() { return Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? this.dataDoc : this.layoutDoc; }
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
-        !Doc.UserDoc().noviceMode && funcs.push({
+        !Doc.noviceMode && funcs.push({
             description: "Clear Script Params", event: () => {
                 const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []);
                 params?.map(p => this.paramsDoc[p] = undefined);
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 5f4c17ee6..0b7264d79 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -471,7 +471,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     onMarqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) {
+        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) {
             setupMoveUpEvents(this, e, action(e => {
                 MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
                 this._marqueeing = [e.clientX, e.clientY];
@@ -570,7 +570,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         //     moveDocument={this.moveDocument}
         //     addDocument={this.sidebarAddDocument}
         //     childPointerEvents={true}
-        //     pointerEvents={CurrentUserUtils.SelectedTool !== InkTool.None || this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />;
+        //     pointerEvents={CurrentUserUtils.ActiveTool !== InkTool.None || this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />;
         return <div className="mapBox" ref={this._ref}>
             {/*console.log(apiKey)*/}
             {/* <LoadScript
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index b14a1f0f6..ddfbf50df 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -428,7 +428,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
       // renders the video and audio
       @computed get content() {
             const field = Cast(this.dataDoc[this.fieldKey], VideoField);
-            const interactive = CurrentUserUtils.SelectedTool !== InkTool.None || !this.props.isSelected() ? "" : "-interactive";
+            const interactive = CurrentUserUtils.ActiveTool !== InkTool.None || !this.props.isSelected() ? "" : "-interactive";
             const classname = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
             return !field ? <div key="loading">Loading</div> :
                   <div className="videoBox-contentContainer" key="container" style={{ mixBlendMode: "multiply" }}>
@@ -484,7 +484,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                   this._disposers.youtubeReactionDisposer?.();
                   this._disposers.reactionDisposer = reaction(() => this.layoutDoc._currentTimecode, () => !this._playing && this.Seek(NumCast(this.layoutDoc._currentTimecode)));
                   this._disposers.youtubeReactionDisposer = reaction(
-                        () => CurrentUserUtils.SelectedTool === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting,
+                        () => CurrentUserUtils.ActiveTool === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting,
                         (interactive) => iframe.style.pointerEvents = interactive ? "all" : "none", { fireImmediately: true });
             };
             if (typeof (YT) === undefined) setTimeout(() => this.loadYouTube(iframe), 100);
@@ -707,7 +707,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
       // starts marquee selection
       marqueeDown = (e: React.PointerEvent) => {
-            if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+            if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.ActiveTool)) {
                   setupMoveUpEvents(this, e, action(e => {
                         MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
                         this._marqueeing = [e.clientX, e.clientY];
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 967158cbf..d14af49ea 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -536,7 +536,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const cm = ContextMenu.Instance;
         const funcs: ContextMenuProps[] = [];
         if (!cm.findByDescription("Options...")) {
-            !Doc.UserDoc().noviceMode && funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" });
+            !Doc.noviceMode && funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" });
             funcs.push({
                 description: (this.layoutDoc.allowScripts ? "Prevent" : "Allow") + " Scripts", event: () => {
                     this.layoutDoc.allowScripts = !this.layoutDoc.allowScripts;
@@ -561,7 +561,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     onMarqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) {
+        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) {
             setupMoveUpEvents(this, e, action(e => {
                 MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
                 this._marqueeing = [e.clientX, e.clientY];
@@ -678,7 +678,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth))
 
     @computed get content() {
-        const interactive = !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents?.() !== "none" && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting;
+        const interactive = !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents?.() !== "none" && CurrentUserUtils.ActiveTool === InkTool.None && !DocumentDecorations.Instance?.Interacting;
         return <div className={"webBox-cont" + (interactive ? "-interactive" : "")}
             onKeyDown={e => e.stopPropagation()}
             style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) || `100%` : "100%", }}>
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index f29dfe489..3af6a3d51 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -1,6 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
+import { StringIterator } from 'lodash';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -70,7 +71,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
     useAsPrototype = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'); };
 
     specificContextMenu = (): void => {
-        if (!Doc.UserDoc().noviceMode) {
+        if (!Doc.noviceMode) {
             const cm = ContextMenu.Instance;
             cm.addItem({ description: "Show Template", event: this.showTemplate, icon: "tag" });
             cm.addItem({ description: "Use as Render Template", event: this.dragAsTemplate, icon: "tag" });
@@ -269,7 +270,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
 
         // Get items to place into the list
         const list = this.buttonList.map((value) => {
-            if (Doc.UserDoc().noviceMode && !noviceList.includes(value)) {
+            if (Doc.noviceMode && !noviceList.includes(value)) {
                 return;
             }
             return <div className="list-item" key={`${value}`}
@@ -567,8 +568,8 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole
 // toggle: Set overlay status of selected document
 ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
     const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
-    if (checkResult && selected) {
-        if (NumCast(selected.Document.z) >= 1) return Colors.MEDIUM_BLUE;
+    if (checkResult) {
+        if (NumCast(selected?.Document.z) >= 1) return Colors.MEDIUM_BLUE;
         return "transparent";
     }
     selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("[FontIconBox.tsx] toggleOverlay failed");
@@ -710,7 +711,7 @@ ScriptingGlobals.add(function toggleItalic(checkResult?: boolean) {
 
 export function checkInksToGroup() {
     // console.log("getting here to inks group");
-    if (CurrentUserUtils.SelectedTool === InkTool.Write) {
+    if (CurrentUserUtils.ActiveTool === InkTool.Write) {
         CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
             // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
             // find all inkDocs in ffView.unprocessedDocs that are within 200 pixels of each other
@@ -723,7 +724,7 @@ export function checkInksToGroup() {
 
 export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
     // TODO nda - if document being added to is a inkGrouping then we can just add to that group
-    if (CurrentUserUtils.SelectedTool === InkTool.Write) {
+    if (CurrentUserUtils.ActiveTool === InkTool.Write) {
         CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
             // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
             const selected = ffView.unprocessedDocs;
@@ -785,38 +786,38 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
 
 
 /** INK
- *      setActiveInkTool
+ *      setActiveTool
  *      setStrokeWidth
  *      setStrokeColor
  **/
 
-ScriptingGlobals.add(function setActiveInkTool(tool: string, checkResult?: boolean) {
+ScriptingGlobals.add(function setActiveTool(tool: string, checkResult?: boolean) {
     InkTranscription.Instance?.createInkGroup();
     if (checkResult) {
-        return ((Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool) ?
+        return ((CurrentUserUtils.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool) ?
             Colors.MEDIUM_BLUE : "transparent";
     }
     if (["circle", "square", "line"].includes(tool)) {
         if (GestureOverlay.Instance.InkShape === tool) {
-            Doc.UserDoc().activeInkTool = InkTool.None;
+            CurrentUserUtils.ActiveTool = InkTool.None;
             GestureOverlay.Instance.InkShape = InkTool.None;
         } else {
-            Doc.UserDoc().activeInkTool = InkTool.Pen;
+            CurrentUserUtils.ActiveTool = InkTool.Pen;
             GestureOverlay.Instance.InkShape = tool;
         }
     } else if (tool) { // pen or eraser
-        if (Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance.InkShape) {
-            Doc.UserDoc().activeInkTool = InkTool.None;
-        } else if (tool == "write") {
+        if (CurrentUserUtils.ActiveTool === tool && !GestureOverlay.Instance.InkShape) {
+            CurrentUserUtils.ActiveTool = InkTool.None;
+        } else if (tool == InkTool.Write) {
             // console.log("write mode selected - create groupDoc here!", tool)
-            Doc.UserDoc().activeInkTool = tool;
+            CurrentUserUtils.ActiveTool = tool;
             GestureOverlay.Instance.InkShape = "";
         } else {
-            Doc.UserDoc().activeInkTool = tool;
+            CurrentUserUtils.ActiveTool = tool as any;
             GestureOverlay.Instance.InkShape = "";
         }
     } else {
-        Doc.UserDoc().activeInkTool = InkTool.None;
+        CurrentUserUtils.ActiveTool = InkTool.None;
     }
 });
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 16a523b40..90199618b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -648,7 +648,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const highlighting: ContextMenuProps[] = [];
         const noviceHighlighting = ["Audio Tags", "My Text", "Text from Others", "Bold Text"];
         const expertHighlighting = [...noviceHighlighting, "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"];
-        (Doc.UserDoc().noviceMode ? noviceHighlighting : expertHighlighting).forEach(option =>
+        (Doc.noviceMode ? noviceHighlighting : expertHighlighting).forEach(option =>
             highlighting.push({
                 description: (FormattedTextBox._globalHighlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
                     e.stopPropagation();
@@ -663,11 +663,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             }));
 
         const uicontrols: ContextMenuProps[] = [];
-        !Doc.UserDoc().noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ""} Show Menu on Selections`, event: () => FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate, icon: "expand-arrows-alt" });
+        !Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ""} Show Menu on Selections`, event: () => FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate, icon: "expand-arrows-alt" });
         uicontrols.push({ description: !this.Document._noSidebar ? "Hide Sidebar Handle" : "Show Sidebar Handle", event: () => this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar, icon: "expand-arrows-alt" });
         uicontrols.push({ description: `${this.layoutDoc._showAudio ? "Hide" : "Show"} Dictation Icon`, event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" });
         uicontrols.push({ description: "Show Highlights...", noexpand: true, subitems: highlighting, icon: "hand-point-right" });
-        !Doc.UserDoc().noviceMode && uicontrols.push({
+        !Doc.noviceMode && uicontrols.push({
             description: "Broadcast Message", event: () => DocServer.GetRefField("rtfProto").then(proto =>
                 proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt"
         });
@@ -677,7 +677,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
         appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" });
         // this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
-        !Doc.UserDoc().noviceMode && appearanceItems.push({
+        !Doc.noviceMode && appearanceItems.push({
             description: "Make Default Layout", event: () => {
                 if (!this.layoutDoc.isTemplateDoc) {
                     const title = StrCast(this.rootDoc.title);
@@ -1636,7 +1636,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     noSidebar={true}
                     fieldKey={this.layoutDoc.sidebarViewType === "translation" ? `${this.fieldKey}-translation` : `${this.fieldKey}-annotations`} />;
         };
-        return <div className={"formattedTextBox-sidebar" + (CurrentUserUtils.SelectedTool !== InkTool.None ? "-inking" : "")}
+        return <div className={"formattedTextBox-sidebar" + (CurrentUserUtils.ActiveTool !== InkTool.None ? "-inking" : "")}
             style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
             {renderComponent(StrCast(this.layoutDoc.sidebarViewType))}
         </div>;
@@ -1647,7 +1647,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const active = this.props.isContentActive();
         const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
         const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
-        const interactive = (CurrentUserUtils.SelectedTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition);
+        const interactive = (CurrentUserUtils.ActiveTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition);
         if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide);
         const minimal = this.props.ignoreAutoHeight;
         const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0);
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 9bc2e5628..98343a261 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -67,7 +67,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
         runInAction(() => {
             RichTextMenu.Instance = this;
             this._canFade = false;
-            //this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]);
             this.Pinned = true;
         });
     }
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 52b03b0a5..0d2cffc2c 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -134,7 +134,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed get presElement() { return Cast(Doc.UserDoc().presElement, Doc, null); }
     constructor(props: any) {
         super(props);
-        if (Doc.UserDoc().activePresentation = this.rootDoc) runInAction(() => PresBox.Instance = this);
+        if (CurrentUserUtils.ActivePresentation = this.rootDoc) runInAction(() => PresBox.Instance = this);
         if (!this.presElement) { // create exactly one presElmentBox template to use by any and all presentations.
             Doc.UserDoc().presElement = new PrefetchProxy(Docs.Create.PresElementBoxDocument({
                 title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data"
@@ -178,16 +178,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.layoutDoc._gridGap = 0;
         this.layoutDoc._yMargin = 0;
         this.turnOffEdit(true);
-        DocListCastAsync((Doc.UserDoc().myTrails as Doc).data).then(pres =>
-            !pres?.includes(this.rootDoc) && Doc.AddDocToList(Doc.UserDoc().myTrails as Doc, "data", this.rootDoc));
+        DocListCastAsync(CurrentUserUtils.MyTrails.data).then(pres =>
+            !pres?.includes(this.rootDoc) && Doc.AddDocToList(CurrentUserUtils.MyTrails, "data", this.rootDoc));
         this._disposers.selection = reaction(() => SelectionManager.Views(),
             views => views.some(view => view.props.Document === this.rootDoc) && this.updateCurrentPresentation());
     }
 
     @action
     updateCurrentPresentation = (pres?: Doc) => {
-        if (pres) Doc.UserDoc().activePresentation = pres;
-        else Doc.UserDoc().activePresentation = this.rootDoc;
+        if (pres) CurrentUserUtils.ActivePresentation = pres;
+        else CurrentUserUtils.ActivePresentation = this.rootDoc;
         document.removeEventListener("keydown", PresBox.keyEventsWrapper, true);
         document.addEventListener("keydown", PresBox.keyEventsWrapper, true);
         this._presKeyEventsActive = true;
@@ -623,9 +623,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
      */
     @action
     updateMinimize = async () => {
-        if (CurrentUserUtils.MyOverlayDocs.includes(this.layoutDoc)) {
+        if (DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc)) {
             this.layoutDoc.presStatus = PresStatus.Edit;
-            Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc);
+            Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, this.rootDoc);
             CollectionDockingView.AddSplit(this.rootDoc, "right");
         } else {
             this.layoutDoc.presStatus = PresStatus.Edit;
@@ -635,7 +635,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             this.rootDoc.y = pt[1] + 10;
             this.rootDoc._height = 30;
             this.rootDoc._width = 248;
-            Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc);
+            Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, this.rootDoc);
             this.props.removeDocument?.(this.layoutDoc);
         }
     }
@@ -732,7 +732,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc);
     getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
     panelHeight = () => this.props.PanelHeight() - 40;
-    isContentActive = (outsideReaction?: boolean) => ((CurrentUserUtils.SelectedTool === InkTool.None && !this.layoutDoc._lockedPosition) &&
+    isContentActive = (outsideReaction?: boolean) => ((CurrentUserUtils.ActiveTool === InkTool.None && !this.layoutDoc._lockedPosition) &&
         (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
 
     /**
@@ -868,7 +868,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 }
                 break;
             case "Escape":
-                if (CurrentUserUtils.MyOverlayDocs.includes(this.layoutDoc)) { this.updateMinimize(); }
+                if (DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc)) { this.updateMinimize(); }
                 else if (this.layoutDoc.presStatus === "edit") { this._selectedArray.clear(); this._eleArray.length = this._dragArray.length = 0; }
                 else this.layoutDoc.presStatus = "edit";
                 if (this._presTimer) clearTimeout(this._presTimer);
@@ -2247,7 +2247,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Presentation Panel" : "Open Presentation Panel";
         const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
         const isMini: boolean = this.toolbarWidth <= 100;
-        const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.UserDoc().activePresentation);
+        const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === CurrentUserUtils.ActivePresentation);
         const activeColor = Colors.LIGHT_BLUE;
         const inactiveColor = Colors.WHITE;
         return (mode === CollectionViewType.Carousel3D) ? (null) : (
@@ -2305,7 +2305,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     value={mode}>
                     <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Stacking}>List</option>
                     <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Tree}>Tree</option>
-                    {Doc.UserDoc().noviceMode ? (null) : <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel3D}>3D Carousel</option>}
+                    {Doc.noviceMode ? (null) : <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel3D}>3D Carousel</option>}
                 </select>}
                 <div className="presBox-presentPanel" style={{ opacity: this.childDocs.length ? 1 : 0.3 }}>
                     <span className={`presBox-button ${this.layoutDoc.presStatus === "edit" ? "present" : ""}`}>
@@ -2508,10 +2508,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         // needed to ensure that the childDocs are loaded for looking up fields
         this.childDocs.slice();
         const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
-        const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.UserDoc().activePresentation);
+        const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === CurrentUserUtils.ActivePresentation);
         const presEnd: boolean = !this.layoutDoc.presLoop && (this.itemIndex === this.childDocs.length - 1);
         const presStart: boolean = !this.layoutDoc.presLoop && (this.itemIndex === 0);
-        return CurrentUserUtils.MyOverlayDocs.includes(this.rootDoc) ?
+        return DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.rootDoc) ?
             <div className="miniPres" onClick={e => e.stopPropagation()}>
                 <div className="presPanelOverlay" style={{ display: "inline-flex", height: 30, background: '#323232', top: 0, zIndex: 3000000, boxShadow: presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}>
                     <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : undefined }}
@@ -2536,7 +2536,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 </div>
             </div >
             :
-            <div className="presBox-cont" style={{ minWidth: CurrentUserUtils.MyOverlayDocs.includes(this.layoutDoc) ? 240 : undefined }} >
+            <div className="presBox-cont" style={{ minWidth: DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc) ? 240 : undefined }} >
                 {this.topPanel}
                 {this.toolbar}
                 {this.newDocumentToolbarDropdown}
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 50df00612..1a2f4b93f 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -309,7 +309,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
       @computed get recordingIsInOverlay() {
             let isInOverlay = false
-            DocListCast((Doc.UserDoc().myOverlayDocs as Doc).data).forEach((doc) => {
+            DocListCast(CurrentUserUtils.MyOverlayDocs.data).forEach((doc) => {
                   if (doc.slides === this.rootDoc) {
                         isInOverlay = true
                         return
@@ -319,9 +319,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
       }
 
       removeAllRecordingInOverlay = () => {
-            DocListCast((Doc.UserDoc().myOverlayDocs as Doc).data).forEach((doc) => {
+            DocListCast(CurrentUserUtils.MyOverlayDocs.data).forEach((doc) => {
                   if (doc.slides === this.rootDoc) {
-                        Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc);
+                        Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, doc);
                   }
             })
       }
@@ -339,7 +339,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
             this.removeAllRecordingInOverlay()
             if (activeItem.recording) {
                   // if we already have an existing recording
-                  Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, Cast(activeItem.recording, Doc, null));
+                  Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, Cast(activeItem.recording, Doc, null));
 
             }
       }
@@ -348,15 +348,15 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
       @action
       startRecording = (activeItem: Doc) => {
             // Remove every recording that already exists in overlay view
-            DocListCast((Doc.UserDoc().myOverlayDocs as Doc).data).forEach((doc) => {
+            DocListCast(CurrentUserUtils.MyOverlayDocs.data).forEach((doc) => {
                   if (doc.slides !== null) {
-                        Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc);
+                        Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, doc);
                   }
             })
 
             if (activeItem.recording) {
                   // if we already have an existing recording
-                  Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, Cast(activeItem.recording, Doc, null));
+                  Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, Cast(activeItem.recording, Doc, null));
 
             } else {
                   // if we dont have any recording
@@ -376,7 +376,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                   // make recording box appear in the bottom right corner of the screen
                   recording.x = window.innerWidth - recording[WidthSym]() - 20;
                   recording.y = window.innerHeight - recording[HeightSym]() - 20;
-                  Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, recording);
+                  Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, recording);
             }
       }
 
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 0ded1bb3c..2869d4f2d 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -340,7 +340,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         if ((e.button !== 0 || e.altKey) && this.props.isContentActive(true)) {
             this._setPreviewCursor?.(e.clientX, e.clientY, true, false);
         }
-        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) {
+        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) {
             this.props.select(false);
             MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
             this._marqueeing = [e.clientX, e.clientY];
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 0db3950a2..1de0c0b10 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -27,12 +27,12 @@ import "./TopBar.scss";
 export class TopBar extends React.Component {
       navigateToHome = () => {
             CurrentUserUtils.CaptureDashboardThumbnail()?.then(() => {
-                Doc.UserDoc().activePage = "home";
+                CurrentUserUtils.ActivePage = "home";
                 CurrentUserUtils.closeActiveDashboard(); // bcz: if we do this, we need some other way to keep track, for user convenience, of the last dashboard in use
             });
       }
       render() {
-            const activeDashboard = Cast(Doc.UserDoc().activeDashboard, Doc, null)
+            const activeDashboard = CurrentUserUtils.ActiveDashboard;
             return (
                   //TODO:glr Add support for light / dark mode
                   <div style={{ pointerEvents: "all", background: Colors.DARK_GRAY, borderBottom: Borders.STANDARD }} className="topbar-container">
@@ -44,11 +44,11 @@ export class TopBar extends React.Component {
                                     }}>{Doc.CurrentUserEmail}</div> : (null)}
                               </div>
                               <div className="topbar-center" >
-                                    <div className="topbar-title" onClick={() => SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(activeDashboard)!, false)}>
+                                    <div className="topbar-title" onClick={() => activeDashboard && SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(activeDashboard)!, false)}>
                                           {activeDashboard ? StrCast(activeDashboard.title) : "Dash"}
                                     </div>
                                     <div className="topbar-button-icon" onClick={e => {
-                                        const dashView = DocumentManager.Instance.getDocumentView(activeDashboard);
+                                        const dashView = activeDashboard && DocumentManager.Instance.getDocumentView(activeDashboard);
                                         ContextMenu.Instance.addItem({ description: "Open Dashboard View", event: this.navigateToHome, icon: "edit" });
                                         ContextMenu.Instance.addItem({ description: "Snapshot Dashboard", event: async () => {
                                             const batch = UndoManager.StartBatch("snapshot");
@@ -69,7 +69,7 @@ export class TopBar extends React.Component {
                                     <div className="topbar-button-text" onClick={() => {SharingManager.Instance.open(undefined, activeDashboard)}}>
                                         {/* TODO: if this is my dashboard, display share
                                         if this is a shared dashboard, display "view original or view annotated" */}
-                                        { Doc.GetProto(CurrentUserUtils.ActiveDashboard)?.author ===  Doc.CurrentUserEmail ?  "Share": "view original" }
+                                        { CurrentUserUtils.ActiveDashboard && (Doc.GetProto(CurrentUserUtils.ActiveDashboard)?.author ===  Doc.CurrentUserEmail ?  "Share": "view original") }
                                     </div>
                                     <div className="topbar-button-icon" onClick={() => window.open(
                                           "https://brown-dash.github.io/Dash-Documentation/", "_blank")}>
diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx
index e0d328c89..6c0c9b301 100644
--- a/src/client/views/webcam/DashWebRTCVideo.tsx
+++ b/src/client/views/webcam/DashWebRTCVideo.tsx
@@ -71,7 +71,7 @@ export class DashWebRTCVideo extends React.Component<CollectionFreeFormDocumentV
             </div >;
 
         const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting;
-        const classname = "webBox-cont" + (this.props.isSelected() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
+        const classname = "webBox-cont" + (this.props.isSelected() && CurrentUserUtils.ActiveTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
 
         return (
             <>
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 94be286f5..74213652b 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -21,7 +21,7 @@ import { FieldId, RefField } from "./RefField";
 import { RichTextField } from "./RichTextField";
 import { listSpec } from "./Schema";
 import { ComputedField, ScriptField } from "./ScriptField";
-import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
+import { BoolCast, Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
 import { AudioField, ImageField, MapField, PdfField, VideoField, WebField } from "./URLField";
 import { deleteProperty, GetEffectiveAcl, getField, getter, inheritParentAcls, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util";
 import JSZip = require("jszip");
@@ -233,6 +233,10 @@ export class Doc extends RefField {
     }
 
     private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
+    public static get noviceMode() { return BoolCast(Doc.UserDoc().noviceMode); }
+    public static set noviceMode(val) { Doc.UserDoc().noviceMode = val; }
+    public static get defaultAclPrivate() { return Doc.UserDoc().defaultAclPrivate; }
+    public static set defaultAclPrivate(val) { Doc.UserDoc().defaultAclPrivate = val; }
     public static CurrentUserEmail: string = "";
     public static get CurrentUserEmailNormalized() { return normalizeEmail(Doc.CurrentUserEmail); }
     public async [HandleUpdate](diff: any) {
@@ -805,7 +809,7 @@ export namespace Doc {
             Doc.AddDocToList(Doc.GetProto(copy)[DataSym], "aliases", copy);
         }
         copy.context = undefined;
-        Doc.UserDoc().defaultAclPrivate && (copy["acl-Public"] = "Not Shared");
+        Doc.defaultAclPrivate && (copy["acl-Public"] = "Not Shared");
         if (retitle) {
             copy.title = incrementTitleCopy(StrCast(copy.title));
         }
@@ -861,7 +865,7 @@ export namespace Doc {
             const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + "(..." + _applyCount++ + ")");
             target.layoutKey = targetKey;
             applied && (Doc.GetProto(applied).type = templateDoc.type);
-            Doc.UserDoc().defaultAclPrivate && (applied["acl-Public"] = "Not Shared");
+            Doc.defaultAclPrivate && (applied["acl-Public"] = "Not Shared");
             return applied;
         }
         return undefined;
@@ -1234,19 +1238,19 @@ export namespace Doc {
 
     export function isDocPinned(doc: Doc) {
         //add this new doc to props.Document
-        const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
+        const curPres = CurrentUserUtils.ActivePresentation;
         return !curPres ? false : DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc)) !== -1;
     }
 
     export function copyDragFactory(dragFactory: Doc) {
         const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true);
-        ndoc && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", Doc.GetProto(ndoc));
+        ndoc && Doc.AddDocToList(CurrentUserUtils.MyFileOrphans, "data", Doc.GetProto(ndoc));
         if (ndoc && dragFactory["dragFactory-count"] !== undefined) {
             dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1;
             Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true);
         }
 
-        if (ndoc) inheritParentAcls(CurrentUserUtils.ActiveDashboard, ndoc);
+        if (ndoc && CurrentUserUtils.ActiveDashboard) inheritParentAcls(CurrentUserUtils.ActiveDashboard, ndoc);
 
         return ndoc;
     }
@@ -1448,7 +1452,7 @@ ScriptingGlobals.add(function DOC(id: string) { console.log("Can't parse a docum
 ScriptingGlobals.add(function assignDoc(doc: Doc, field: string, id: string) { return Doc.assignDocToField(doc, field, id); });
 ScriptingGlobals.add(function docCast(doc: FieldResult): any { return DocCastAsync(doc); });
 ScriptingGlobals.add(function activePresentationItem() {
-    const curPres = Doc.UserDoc().activePresentation as Doc;
+    const curPres = CurrentUserUtils.ActivePresentation;
     return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)];
 });
 ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 37b9be31b..8fb35981b 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -145,7 +145,7 @@ export function inheritParentAcls(parent: Doc, child: Doc) {
     const dataDoc = parent[DataSym];
     for (const key of Object.keys(dataDoc)) {
         // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private.
-        const permission = (key === "acl-Public" && Doc.UserDoc().defaultAclPrivate) ? AclPrivate : dataDoc[key];
+        const permission = (key === "acl-Public" && Doc.defaultAclPrivate) ? AclPrivate : dataDoc[key];
         key.startsWith("acl") && distributeAcls(key, permission, child);
     }
 }
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index c5c6fb688..fe8100997 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -51,7 +51,7 @@ library.add(...[faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, fa
 @observer
 export class MobileInterface extends React.Component {
     static Instance: MobileInterface;
-    private _library: Promise<Doc>;
+    private _library: Doc;
     private _mainDoc: any = CurrentUserUtils.setupActiveMobileMenu(Doc.UserDoc());
     @observable private _sidebarActive: boolean = false; //to toggle sidebar display
     @observable private _imageUploadActive: boolean = false; //to toggle image upload
@@ -68,7 +68,7 @@ export class MobileInterface extends React.Component {
 
     constructor(props: Readonly<{}>) {
         super(props);
-        this._library = CurrentUserUtils.setupDashboards(Doc.UserDoc()); // to access documents in Dash Web
+        this._library = CurrentUserUtils.setupDashboards(Doc.UserDoc(), "myDashboards"); // to access documents in Dash Web
         MobileInterface.Instance = this;
     }
 
@@ -76,7 +76,7 @@ export class MobileInterface extends React.Component {
     componentDidMount = () => {
         // if the home menu is in list view -> adjust the menu toggle appropriately
         this._menuListView = this._homeDoc._viewType === "stacking" ? true : false;
-        CurrentUserUtils.SelectedTool = InkTool.None; // ink should intially be set to none
+        CurrentUserUtils.ActiveTool = InkTool.None; // ink should intially be set to none
         Doc.UserDoc().activeMobile = this._homeDoc; // active mobile set to home
         AudioBox.Enabled = true;
 
@@ -125,7 +125,7 @@ export class MobileInterface extends React.Component {
      * Method called when 'Library' button is pressed on the home screen
      */
     switchToLibrary = async () => {
-        this._library.then(library => this.switchCurrentView(library));
+        this.switchCurrentView(this._library);
         runInAction(() => this._homeMenu = false);
         this.toggleSidebar();
     }
@@ -140,7 +140,7 @@ export class MobileInterface extends React.Component {
         // Case 1: Parent document is 'dashboards'
         if (doc === Cast(this._library, Doc) as Doc) {
             this.dashboards = null;
-            this._library.then(library => this.switchCurrentView(library));
+            this.switchCurrentView(this._library);
             // Case 2: Parent document is the 'home' menu (root node)
         } else if (doc === Cast(this._homeDoc, Doc) as Doc) {
             this._homeMenu = true;
@@ -179,7 +179,7 @@ export class MobileInterface extends React.Component {
     @action
     returnMain = () => {
         this._parents = [this._homeDoc];
-        this._library.then(library => this.switchCurrentView(library));
+        this.switchCurrentView(this._library);
         this._homeMenu = false;
         this.dashboards = null;
     }
@@ -419,10 +419,10 @@ export class MobileInterface extends React.Component {
         button.style.color = this._ink ? "black" : "white";
 
         if (!this._ink) {
-            CurrentUserUtils.SelectedTool = InkTool.Pen;
+            CurrentUserUtils.ActiveTool = InkTool.Pen;
             this._ink = true;
         } else {
-            CurrentUserUtils.SelectedTool = InkTool.None;
+            CurrentUserUtils.ActiveTool = InkTool.None;
             this._ink = false;
         }
     }
@@ -514,7 +514,7 @@ export class MobileInterface extends React.Component {
             return <div className="docButton"
                 title={Doc.isDocPinned(this._activeDoc) ? "Unpin from presentation" : "Pin to presentation"}
                 style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }}
-                onClick={e => TabDocView.PinDoc(this._activeDoc, { unpin: isPinned })}>
+                onClick={e => TabDocView.PinDoc(this._activeDoc)}>
                 <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
             </div>;
         } else return (null);
@@ -573,7 +573,7 @@ export class MobileInterface extends React.Component {
     // For setting up the presentation document for the home menu
     @action
     setupDefaultPresentation = () => {
-        const presentation = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
+        const presentation = CurrentUserUtils.ActivePresentation;
 
         if (presentation) {
             this.switchCurrentView(presentation);
-- 
cgit v1.2.3-70-g09d2

