From f072a9f2c1ad69eb8ae4242fa5d2e18bbd94f6ef Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 6 May 2022 11:45:44 -0400
Subject: changed following links to open up icons all the way down to the
 target document. allow StartLink to work with webBox w/ text selection. 
 fixed web clippings to have a cursor and grab keyDown events.

---
 src/client/views/nodes/PDFBox.tsx | 1 +
 1 file changed, 1 insertion(+)

(limited to 'src/client/views/nodes/PDFBox.tsx')

diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 69662d53a..48465976a 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -75,6 +75,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }
 
     updateIcon = () => {
+        return; // currently we render pdf icons as text labels
         const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
         const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
         newDiv.style.width = (this.layoutDoc[WidthSym]()).toString();
-- 
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/nodes/PDFBox.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/nodes/PDFBox.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 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/nodes/PDFBox.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 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/nodes/PDFBox.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 d3e2c8d1be275ed04e0b5164960c3fc60334ce8f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 5 Jun 2022 01:55:26 -0400
Subject: fixed pdf search and page inputs to not iconify with Enter.   fixed
 lightbox to change icons when fitwidth is clicked.

---
 src/client/views/LightboxView.tsx | 2 +-
 src/client/views/nodes/PDFBox.tsx | 6 +++++-
 2 files changed, 6 insertions(+), 2 deletions(-)

(limited to 'src/client/views/nodes/PDFBox.tsx')

diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 3d2769f66..9f890ffad 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -284,7 +284,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                 </div>
                 <div className="lightboxView-navBtn" title={"toggle fit width"}
                     onClick={e => { e.stopPropagation(); LightboxView.LightboxDoc!._fitWidth = !LightboxView.LightboxDoc!._fitWidth; }}>
-                    <FontAwesomeIcon icon={"arrows-alt-h"} size="2x" />
+                    <FontAwesomeIcon icon={LightboxView.LightboxDoc?._fitWidth ? "arrows-alt-h" : "arrows-alt-v"} size="2x" />
                 </div>
             </div>;
     }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index cbe7a5cc6..f2ca6c96e 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -320,7 +320,10 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 <div className="pdfBox-overlayCont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
                     <button className="pdfBox-overlayButton" title={searchTitle} />
                     <input className="pdfBox-searchBar" placeholder="Search" ref={this._searchRef} onChange={this.searchStringChanged}
-                        onKeyDown={e => e.keyCode === KeyCodes.ENTER && this.search(this._searchString, e.shiftKey)} />
+                        onKeyDown={e => {
+                            e.stopPropagation();
+                            e.keyCode === KeyCodes.ENTER && this.search(this._searchString, e.shiftKey);
+                        }} />
                     <button className="pdfBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
                         <FontAwesomeIcon icon="search" size="sm" />
                     </button>
@@ -342,6 +345,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 <div className="pdfBox-pageNums">
                     <input value={curPage} style={{ width: `${curPage > 99 ? 4 : 3}ch`, pointerEvents: "all" }}
                         onChange={e => this.Document._curPage = Number(e.currentTarget.value)}
+                        onKeyDown={e => e.stopPropagation()}
                         onClick={action(() => this._pageControls = !this._pageControls)} />
                     {this._pageControls ? pageBtns : (null)}
                 </div>
-- 
cgit v1.2.3-70-g09d2

