From 1e60b99195bc6748bbad53d173a8a2525216d683 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 18 Mar 2022 08:29:55 -0400
Subject: fixed vertical resizing of web and pdf to not jump after sidebar
 panel is resized

---
 src/client/views/nodes/PDFBox.tsx | 7 +++++--
 1 file changed, 5 insertions(+), 2 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 9807cee7c..fa23dfbe8 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -10,7 +10,7 @@ import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { KeyCodes } from '../../util/KeyCodes';
-import { undoBatch } from '../../util/UndoManager';
+import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent";
@@ -137,18 +137,21 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return this.addDocument(doc, sidebarKey);
     }
     sidebarBtnDown = (e: React.PointerEvent, onButton: boolean) => { // onButton determines whether the width of the pdf box changes, or just the ratio of the sidebar to the pdf
+        const batch = UndoManager.StartBatch("sidebar");
         setupMoveUpEvents(this, e, (e, down, delta) => {
             const localDelta = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformDirection(delta[0], delta[1]);
             const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
+            const nativeHeight = NumCast(this.layoutDoc[this.fieldKey + "-nativeHeight"]);
             const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
             const ratio = (curNativeWidth + (onButton ? 1 : -1) * localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth;
             if (ratio >= 1) {
                 this.layoutDoc.nativeWidth = nativeWidth * ratio;
+                this.layoutDoc.nativeHeight = nativeHeight * (1 + ratio);
                 onButton && (this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]);
                 this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
             }
             return false;
-        }, emptyFunction, () => this.toggleSidebar());
+        }, () => batch.end(), () => this.toggleSidebar());
     }
     @observable _previewNativeWidth: Opt<number> = undefined;
     @observable _previewWidth: Opt<number> = undefined;
-- 
cgit v1.2.3-70-g09d2


From 70cd320fb02993b2abb17dcfb2a3fd1dfefabb36 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 23 Mar 2022 18:30:59 -0400
Subject: adjsuted pdfBox and WebBox to resize better without glitches when
 using the annotation sidebar.  changed so that when fitWidth is set anything
 can be resized vertically - causes artifacts for videoBoxes.

---
 src/client/views/DocumentDecorations.tsx | 2 +-
 src/client/views/nodes/PDFBox.tsx        | 8 +++-----
 src/client/views/nodes/WebBox.tsx        | 7 +++----
 src/fields/Doc.ts                        | 7 ++++++-
 4 files changed, 13 insertions(+), 11 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index d0b5bfe46..39591e417 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -339,7 +339,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                 }
                 let actualdW = Math.max(width + (dW * scale), 20);
                 let actualdH = Math.max(height + (dH * scale), 20);
-                const fixedAspect = (nwidth && nheight);
+                const fixedAspect = (nwidth && nheight && !doc._fitWidth);
                 if (fixedAspect) {
                     if ((Math.abs(dW) > Math.abs(dH) && (!dragBottom || !modifyNativeDim)) || dragRight) {
                         if (dragRight && modifyNativeDim) {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index fa23dfbe8..a2e7d2aa3 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -141,12 +141,10 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         setupMoveUpEvents(this, e, (e, down, delta) => {
             const localDelta = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformDirection(delta[0], delta[1]);
             const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
-            const nativeHeight = NumCast(this.layoutDoc[this.fieldKey + "-nativeHeight"]);
             const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
             const ratio = (curNativeWidth + (onButton ? 1 : -1) * localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth;
             if (ratio >= 1) {
                 this.layoutDoc.nativeWidth = nativeWidth * ratio;
-                this.layoutDoc.nativeHeight = nativeHeight * (1 + ratio);
                 onButton && (this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]);
                 this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
             }
@@ -227,8 +225,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             </div>;
     }
     sidebarWidth = () => !this.SidebarShown ? 0 :
-        this._previewWidth ? PDFBox.openSidebarWidth :
-            (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth)
+        PDFBox.sidebarResizerWidth + (this._previewWidth ? PDFBox.openSidebarWidth :
+            (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth))
 
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
@@ -264,7 +262,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             }}>
             <div className="pdfBox-background" onPointerDown={e => this.sidebarBtnDown(e, false)} />
             <div style={{
-                width: `calc(${100 / scale}% - ${(this.sidebarWidth() + PDFBox.sidebarResizerWidth) / scale * (this._previewWidth ? scale : 1)}px)`,
+                width: `calc(${100 / scale}% - ${this.sidebarWidth() / scale * (this._previewWidth ? scale : 1)}px)`,
                 height: `${100 / scale}%`,
                 transform: `scale(${scale})`,
                 position: "absolute",
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 7f314bddc..7969546c5 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -621,9 +621,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     });
     sidebarWidth = () => !this.SidebarShown ? 0 :
-        this._previewWidth ? WebBox.openSidebarWidth :
-            (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() /
-            NumCast(this.layoutDoc.nativeWidth)
+        WebBox.sidebarResizerWidth + (this._previewWidth ? WebBox.openSidebarWidth :
+            (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;
@@ -715,7 +714,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 <div className="webBox-background" onPointerDown={e => this.sidebarBtnDown(e, false)} />
                 <div className="webBox-container" style={{
                     position: "absolute",
-                    width: `calc(${100 / scale}% - ${(this.sidebarWidth() + WebBox.sidebarResizerWidth) / scale * (this._previewWidth ? scale : 1)}px)`,
+                    width: `calc(${100 / scale}% - ${this.sidebarWidth() / scale * (this._previewWidth ? scale : 1)}px)`,
                     transform: `scale(${scale})`,
                     pointerEvents
                 }} onContextMenu={this.specificContextMenu}>
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 1253cf9c7..c932259de 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -346,6 +346,7 @@ export namespace Doc {
         return GetT(doc, "system", "boolean", true);
     }
     export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) {
+        if (key.startsWith("_")) key = key.substring(1);
         const hasProto = doc.proto instanceof Doc;
         const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1;
         const onProto = hasProto && Object.getOwnPropertyNames(doc.proto).indexOf(key) !== -1;
@@ -962,7 +963,11 @@ export namespace Doc {
         return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
     }
     export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) { return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + "-nativeWidth"], useWidth ? doc[WidthSym]() : 0)); }
-    export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) { return !doc ? 0 : NumCast(doc._nativeHeight, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + "-nativeHeight"], useHeight ? doc[HeightSym]() : 0)); }
+    export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) {
+        const dheight = doc ? NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + "-nativeHeight"], useHeight ? doc[HeightSym]() : 0) : 0;
+        const nheight = doc ? Doc.NativeWidth(doc, dataDoc, useHeight) * doc[HeightSym]() / doc[WidthSym]() : 0;
+        return !doc ? 0 : NumCast(doc._nativeHeight, nheight || dheight);
+    }
     export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) { doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + "-nativeWidth"] = width; }
     export function SetNativeHeight(doc: Doc, height: number | undefined, fieldKey?: string) { doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + "-nativeHeight"] = height; }
 
-- 
cgit v1.2.3-70-g09d2


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

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

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

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


From fd58a5f8598815ab1ab3893e60973654a59a52c1 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 12 Apr 2022 10:54:28 -0400
Subject: cleaned up errors/warnings.   fixed dragging to turn off pointer
 events on whatever's being dragged.

---
 src/client/util/DocumentManager.ts                             | 10 ++++++----
 src/client/util/DragManager.ts                                 |  2 +-
 src/client/views/StyleProvider.tsx                             |  4 +++-
 src/client/views/collections/CollectionDockingView.tsx         |  4 ++--
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx      |  4 ++--
 src/client/views/nodes/LabelBox.tsx                            |  7 +------
 src/client/views/nodes/PDFBox.tsx                              |  6 +++---
 src/client/views/nodes/VideoBox.tsx                            |  2 +-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx      |  2 +-
 10 files changed, 21 insertions(+), 22 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 6076de055..1d603e18f 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -169,10 +169,12 @@ export class DocumentManager {
         const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
         const docView = getFirstDocView(targetDoc, originatingDoc);
         const wasHidden = targetDoc.hidden;  // 
-        if (wasHidden) runInAction(() => {
-            targetDoc.hidden = false;
-            docView?.props.bringToFront(targetDoc);
-        });  // if the target is hidden, un-hide it here.
+        if (wasHidden) {
+            runInAction(() => {
+                targetDoc.hidden = false;
+                docView?.props.bringToFront(targetDoc);
+            });  // if the target is hidden, un-hide it here.
+        }
         const focusAndFinish = (didFocus: boolean) => {
             if (originatingDoc?.isPushpin) {
                 if (!didFocus && !wasHidden) {  // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 94a09eac4..13e5940d6 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -405,7 +405,7 @@ export namespace DragManager {
                 Array.from(pdfBox).filter(pb => pb.width && pb.height).map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0));
             }
             [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele =>
-                ele.hasAttribute("style") && ((ele as any).style.pointerEvents = "none"));
+                (ele as any).style && ((ele as any).style.pointerEvents = "none"));
 
             dragDiv.appendChild(dragElement);
             if (dragElement !== ele) {
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 2ce78f64f..9803f9782 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -149,7 +149,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                                     (doc?._isGroup ? undefined :
                                         StrCast((props?.renderDepth || 0) > 0 ?
                                             Doc.UserDoc().activeCollectionNestedBackground :
-                                            Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ? Colors.BLACK : Colors.WHITE))
+                                            Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ?
+                                                Colors.BLACK :
+                                                "linear-gradient(#065fff, #85c1f9)"))
                                     ));
                     break;
                 //if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)";
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 6931d9896..e95371aaa 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -92,7 +92,7 @@ export class CollectionDockingView extends CollectionSubView() {
                 DragManager.CompleteWindowDrag = undefined;
             }
             finishDrag?.(aborted);
-        }
+        };
     }
     @undoBatch
     public CloseFullScreen = () => {
@@ -316,7 +316,7 @@ export class CollectionDockingView extends CollectionSubView() {
             }
             this._goldenLayout.init();
             this._goldenLayout.root.layoutManager.on('itemDropped', this.tabItemDropped);
-            this._goldenLayout.root.layoutManager.on('dragStart', this.tabDragStart)
+            this._goldenLayout.root.layoutManager.on('dragStart', this.tabDragStart);
             this._goldenLayout.root.layoutManager.on('activeContentItemChanged', this.stateChanged);
         }
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 04beed541..9b0acffdf 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1417,7 +1417,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     updateIcon = () => {
         this.props.docViewPath().lastElement().ContentDiv!.style.width = (this.layoutDoc[WidthSym]()).toString();
         this.props.docViewPath().lastElement().ContentDiv!.style.height = (this.layoutDoc[HeightSym]()).toString();
-        var htmlString = this._mainCont && new XMLSerializer().serializeToString(this.props.docViewPath().lastElement().ContentDiv!);
+        const htmlString = this._mainCont && new XMLSerializer().serializeToString(this.props.docViewPath().lastElement().ContentDiv!);
         this.props.docViewPath().lastElement().ContentDiv!.style.width = "";
         this.props.docViewPath().lastElement().ContentDiv!.style.height = "";
         const nativeWidth = this.layoutDoc[WidthSym]();
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 7fb2c235a..3e35039e3 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -168,8 +168,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             PanelWidth: this.panelWidth,
             PanelHeight: this.panelHeight,
         };
-        var background = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        const mixBlendMode = StrCast(this.layoutDoc.mixBlendMode) as any || (typeof background === "string" && background && DashColor(background).alpha() !== 1 ? "multiply" : undefined);
+        const background = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+        const mixBlendMode = StrCast(this.layoutDoc.mixBlendMode) as any || (typeof background === "string" && background && (!background.startsWith("linear") && DashColor(background).alpha() !== 1) ? "multiply" : undefined);
         return <div className={"collectionFreeFormDocumentView-container"}
             style={{
                 outline: this.Highlight ? "orange solid 2px" : "",
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 3405a7635..d539ca9b8 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -30,11 +30,6 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro
 
     componentDidMount() {
         this.props.setContentView?.(this);
-        console.log("MOUNTING")
-    }
-
-    componentDidUpdate() {
-        console.log("UPDATING")
     }
 
     getTitle() {
@@ -89,7 +84,7 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro
             verticalAlign: "center",
             textAlign: "center",
             whiteSpace: "nowrap"
-        })
+        });
     }
     // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")")
     render() {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 3f1771e68..c73823a2b 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -63,7 +63,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
         if (oldDiv instanceof HTMLCanvasElement) {
             const canvas = oldDiv as HTMLCanvasElement;
-            var img = document.createElement('img'); // create a Image Element
+            const img = document.createElement('img'); // create a Image Element
             img.src = canvas.toDataURL();   //image source
             img.style.width = canvas.style.width;
             img.style.height = canvas.style.height;
@@ -79,8 +79,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
         newDiv.style.width = (this.layoutDoc[WidthSym]()).toString();
         newDiv.style.height = (this.layoutDoc[HeightSym]()).toString();
-        this.replaceCanvases(docViewContent, newDiv)
-        var htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv);
+        this.replaceCanvases(docViewContent, newDiv);
+        const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv);
         const nativeWidth = this.layoutDoc[WidthSym]();
         const nativeHeight = this.layoutDoc[HeightSym]();
 
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index b7b8ce064..c31d38b0d 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -265,7 +265,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             this.dataDoc.icon = new ImageField(returnedfilename);
             this.dataDoc["icon-nativeWidth"] = this.layoutDoc[WidthSym]();
             this.dataDoc["icon-nativeHeight"] = this.layoutDoc[HeightSym]();
-        }
+        };
         this.Snapshot(undefined, undefined, makeIcon);
     }
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ec88b8d1d..b6713d878 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -364,7 +364,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     updateTitle = () => {
-        const title = StrCast(this.dataDoc.title)
+        const title = StrCast(this.dataDoc.title);
         if (!this.props.dontRegisterView &&  // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
             (title.startsWith("-") || title.startsWith("@")) && this._editorView && !this.dataDoc["title-custom"] &&
             (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === "text")) {
-- 
cgit v1.2.3-70-g09d2


From a0b30f735850d4543cf9499ebd497b9548c22e47 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 14 Apr 2022 12:25:31 -0400
Subject: tweaks.  fixed groups to be resizable when iconified.

---
 src/Utils.ts                             | 1 +
 src/client/views/DocumentDecorations.tsx | 2 +-
 src/client/views/nodes/PDFBox.tsx        | 2 +-
 3 files changed, 3 insertions(+), 2 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 77095005b..d1d400de7 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -604,6 +604,7 @@ export function DashColor(color: string) {
 
 export function lightOrDark(color: any) {
     if (color === "transparent") return "gray";
+    if (color.startsWith?.("linear")) return "black";
     const nonAlphaColor = color.startsWith("#") ? (color as string).substring(0, 7) :
         color.startsWith("rgba") ? color.replace(/,.[^,]*\)/, ")").replace("rgba", "rgb") : color;
     const col = DashColor(nonAlphaColor).rgb();
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 78a8a4c7e..e412d39ac 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -463,7 +463,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
             return (null);
         }
-        const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles || seldoc.rootDoc.isGroup;
+        const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles || seldoc.rootDoc._isGroup;
         const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle;
         const canOpen = SelectionManager.Views().some(docView => !docView.props.Document._stayInCollection && !docView.props.Document.isGroup && !docView.props.Document.hideOpenButton);
         const canDelete = this.canDelete;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index c73823a2b..54c4be24d 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -62,7 +62,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             }
         }
         if (oldDiv instanceof HTMLCanvasElement) {
-            const canvas = oldDiv as HTMLCanvasElement;
+            const canvas = oldDiv;
             const img = document.createElement('img'); // create a Image Element
             img.src = canvas.toDataURL();   //image source
             img.style.width = canvas.style.width;
-- 
cgit v1.2.3-70-g09d2


From f4b6942fc1cad1f7e7ec7552e22f6c56bc158a77 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 26 Apr 2022 14:35:49 -0400
Subject: made creating a new thumbnail icon delete previous ones.  fixed bug
 where documents would re-render if an earlier docuent in the collection list
 was added/removed.

---
 .../collectionFreeForm/CollectionFreeFormView.tsx            |  7 +++----
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx    |  3 +--
 src/client/views/nodes/PDFBox.tsx                            |  2 +-
 src/client/views/nodes/VideoBox.tsx                          |  5 +++--
 src/client/views/nodes/WebBox.tsx                            |  2 +-
 src/server/ApiManagers/UploadManager.ts                      | 12 ++++++++----
 6 files changed, 17 insertions(+), 14 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a14405a0b..bdae5a4f8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1147,7 +1147,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     isContentActive = () => this.props.isSelected() || this.props.isContentActive();
 
-    getChildDocView(entry: PoolData, renderIndex: number) {
+    getChildDocView(entry: PoolData) {
         const childLayout = entry.pair.layout;
         const childData = entry.pair.data;
         const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
@@ -1156,7 +1156,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             Document={childLayout}
             renderDepth={this.props.renderDepth + 1}
             replica={entry.replica}
-            renderIndex={renderIndex}
             dataTransition={entry.transition}
             suppressSetHeight={this.layoutEngine ? true : false}
             renderCutoffProvider={this.renderCutoffProvider}
@@ -1335,7 +1334,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const elements = computedElementData.slice();
         Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach((entry, i) =>
             elements.push({
-                ele: this.getChildDocView(entry[1], i),
+                ele: this.getChildDocView(entry[1]),
                 bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica)
             }));
 
@@ -1436,7 +1435,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             NumCast(this.layoutDoc._scrollTop)
         ).then
             ((data_url: any) => {
-                VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true).then(
+                VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true, this.layoutDoc[Id] + "-icon").then(
                     returnedfilename => setTimeout(action(() => {
 
                         this.dataDoc.icon = new ImageField(returnedfilename);
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 6097425e1..3f16d3c49 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, Opt } from "../../../fields/Doc";
 import { List } from "../../../fields/List";
@@ -28,7 +28,6 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     jitterRotation: number;
     dataTransition?: string;
     replica: string;
-    renderIndex: number;
     CollectionFreeFormView: CollectionFreeFormView;
 }
 
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 54c4be24d..23749d479 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -93,7 +93,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             NumCast(this.layoutDoc._scrollTop) * this.props.PanelHeight() / NumCast(this.rootDoc[this.fieldKey + "-nativeHeight"])
         ).then
             ((data_url: any) => {
-                VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true).then(
+                VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true, this.layoutDoc[Id] + "-icon").then(
                     returnedfilename => setTimeout(action(() => {
                         this.dataDoc.icon = new ImageField(returnedfilename);
                         this.dataDoc["icon-nativeWidth"] = nativeWidth;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index c31d38b0d..e57cb1abe 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -53,14 +53,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
      * @param returnedFilename the base filename to store the image on the server
      * @param nosuffix optionally suppress creating multiple resolution images
      */
-    public static async convertDataUri(imageUri: string, returnedFilename: string, nosuffix = false) {
+    public static async convertDataUri(imageUri: string, returnedFilename: string, nosuffix = false, replaceRootFilename?: string) {
         try {
             const posting = Utils.prepend("/uploadURI");
             const returnedUri = await rp.post(posting, {
                 body: {
                     uri: imageUri,
                     name: returnedFilename,
-                    nosuffix
+                    nosuffix,
+                    replaceRootFilename
                 },
                 json: true,
             });
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index eeb7b925b..6a3c6336d 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -150,7 +150,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             NumCast(this.layoutDoc._scrollTop)
                         ).then
                             ((data_url: any) => {
-                                VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-thumb" + (new Date()).getTime(), true).then(
+                                VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true, this.layoutDoc[Id] + "-icon").then(
                                     returnedfilename => setTimeout(action(() => this.layoutDoc.thumb = new ImageField(returnedfilename)), 500));
                             })
                             .catch(function (error: any) {
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index bfa07d47a..5e0cd67cc 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -3,7 +3,7 @@ import { Method, _success } from "../RouteManager";
 import * as formidable from 'formidable';
 import v4 = require('uuid/v4');
 const AdmZip = require('adm-zip');
-import { extname, basename, dirname } from 'path';
+import { extname, basename, dirname, } from 'path';
 import { createReadStream, createWriteStream, unlink, writeFile } from "fs";
 import { publicDirectory, filesDirectory } from "..";
 import { Database } from "../database";
@@ -13,10 +13,8 @@ import { AcceptableMedia, Upload } from "../SharedMediaTypes";
 import { normalize } from "path";
 import RouteSubscriber from "../RouteSubscriber";
 const imageDataUri = require('image-data-uri');
-import { isWebUri } from "valid-url";
-import { Opt } from "../../fields/Doc";
 import { SolrManager } from "./SearchManager";
-import { StringDecoder } from "string_decoder";
+let fs = require('fs');
 
 export enum Directory {
     parsed_files = "parsed_files",
@@ -260,10 +258,16 @@ export default class UploadManager extends ApiManager {
                 const uri = req.body.uri;
                 const filename = req.body.name;
                 const origSuffix = req.body.nosuffix ? SizeSuffix.None : SizeSuffix.Original;
+                const deleteFiles = req.body.replaceRootFilename;
                 if (!uri || !filename) {
                     res.status(401).send("incorrect parameters specified");
                     return;
                 }
+                if (deleteFiles) {
+                    const path = serverPathToFile(Directory.images, "");
+                    let regex = new RegExp(`${deleteFiles}.*`);
+                    fs.readdirSync(path).filter((f: any) => regex.test(f)).map((f: any) => fs.unlinkSync(path + f));
+                }
                 return imageDataUri.outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, origSuffix))).then((savedName: string) => {
                     const ext = extname(savedName).toLowerCase();
                     const { pngs, jpgs } = AcceptableMedia;
-- 
cgit v1.2.3-70-g09d2


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

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

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

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


From 59a22f3a007b201e68ac2f3e1d62e0ca5e66488a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 28 Apr 2022 12:13:44 -0400
Subject: fixed feedback dragging link button. fixed warnings.  added some
 assymetric linkRelationships.

---
 src/client/documents/Documents.ts                              |  3 ++-
 src/client/util/DocumentManager.ts                             |  2 +-
 src/client/util/LinkManager.ts                                 |  2 +-
 src/client/views/SidebarAnnos.tsx                              |  2 +-
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../views/collections/collectionFreeForm/MarqueeView.tsx       |  2 +-
 src/client/views/linking/LinkMenu.tsx                          |  4 +---
 src/client/views/nodes/DocumentLinksButton.scss                |  2 ++
 src/client/views/nodes/DocumentLinksButton.tsx                 | 10 +++++-----
 src/client/views/nodes/DocumentView.tsx                        |  4 ++--
 src/client/views/nodes/ImageBox.tsx                            |  2 +-
 src/client/views/nodes/PDFBox.tsx                              |  4 ++--
 src/client/views/nodes/formattedText/RichTextRules.ts          |  2 +-
 src/client/views/search/SearchBox.tsx                          |  2 +-
 src/server/ApiManagers/UploadManager.ts                        |  4 ++--
 15 files changed, 24 insertions(+), 23 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 0bb873667..087d32a60 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1094,10 +1094,11 @@ export namespace DocUtils {
     export function MakeLinkToActiveAudio(getSourceDoc: () => Doc, broadcastEvent = true) {
         broadcastEvent && runInAction(() => DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1);
         return DocUtils.ActiveRecordings.map(audio =>
-            DocUtils.MakeLink({ doc: getSourceDoc() }, { doc: audio.getAnchor() || audio.props.Document }, "recording link", "recording timeline"));
+            DocUtils.MakeLink({ doc: getSourceDoc() }, { doc: audio.getAnchor() || audio.props.Document }, "recording annotation:linked recording", "recording timeline"));
     }
 
     export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) {
+        if (!linkRelationship) linkRelationship = target.doc.type === DocumentType.RTF ? "Commentary:Comments On" : "link";
         const sv = DocumentManager.Instance.getDocumentView(source.doc);
         if (!allowParCollectionLink && sv?.props.ContainingCollectionDoc === target.doc) return;
         if (target.doc === Doc.UserDoc()) return undefined;
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 412d5a169..b6c28d2fe 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -167,7 +167,7 @@ export class DocumentManager {
     ): Promise<void> => {
         originalTarget = originalTarget ?? targetDoc;
         const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
-        var docView = getFirstDocView(targetDoc, originatingDoc);
+        const docView = getFirstDocView(targetDoc, originatingDoc);
         const annotatedDoc = Cast(targetDoc.annotationOn, Doc, null);
         const resolvedTarget = targetDoc.type === DocumentType.MARKER ? annotatedDoc ?? targetDoc : targetDoc; // if target is a marker, then focus toggling should apply to the document it's on since the marker itself doesn't have a hidden field
         var wasHidden = resolvedTarget.hidden;
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 7ba7a1d4c..9445533dc 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -152,7 +152,7 @@ export class LinkManager {
         this.relatedLinker(anchor).forEach(link => {
             if (link.linkRelationship && link.linkRelationship !== "-ungrouped-") {
                 const relation = StrCast(link.linkRelationship);
-                const anchorRelation = relation.indexOf(":") ? relation.split(":")[Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), anchor) ? 0 : 1] : relation;
+                const anchorRelation = relation.indexOf(":") !== -1 ? relation.split(":")[Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), anchor) ? 0 : 1] : relation;
                 const group = anchorGroups.get(anchorRelation);
                 anchorGroups.set(anchorRelation, group ? [...group, link] : [link]);
             } else {
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 43a02d029..04c0565ea 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -61,7 +61,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
         FormattedTextBox.SelectOnLoad = target[Id];
         FormattedTextBox.DontSelectInitialText = true;
         this.allMetadata.map(tag => target[tag] = tag);
-        DocUtils.MakeLink({ doc: anchor }, { doc: target }, "inline markup");
+        DocUtils.MakeLink({ doc: anchor }, { doc: target }, "inline comment:comment on");
         this.addDocument(target);
         this._stackRef.current?.focusDocument(target);
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f0b3d70a0..3f72052ae 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -278,7 +278,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             if (!linkDragData.linkDragView.props.CollectionFreeFormDocumentView?.() || linkDragData.dragDocument.context !== this.props.Document) { // if the source doc view's context isn't this same freeformcollectionlinkDragData.dragDocument.context === this.props.Document
                 const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
                 this.props.addDocument?.(source);
-                de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceGetAnchor() }, "doc annotation", ""); // TODODO this is where in text links get passed
+                de.complete.linkDocument = DocUtils.MakeLink({ doc: linkDragData.linkSourceGetAnchor() }, { doc: source }, "annotated by:annotation of", ""); // TODODO this is where in text links get passed
             }
             e.stopPropagation();  //  do nothing if link is dropped into any freeform view parent of dragged document
             return true;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 2174aaf10..5f303b428 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -521,7 +521,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         });
         const summary = Docs.Create.TextDocument("", { _backgroundColor: "#e2ad32", x: this.Bounds.left, y: this.Bounds.top, isPushpin: true, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "overview" });
         const portal = Docs.Create.FreeformDocument(selected, { x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: "transparent" });
-        DocUtils.MakeLink({ doc: summary }, { doc: portal }, "summarizing", "");
+        DocUtils.MakeLink({ doc: summary }, { doc: portal }, "summary of:summarized by", "");
 
         portal.hidden = true;
         this.props.addDocument?.(portal);
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index a564c59d3..17d28e886 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -1,9 +1,7 @@
-import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
+import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Doc } from "../../../fields/Doc";
 import { LinkManager } from "../../util/LinkManager";
-import { SelectionManager } from "../../util/SelectionManager";
-import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
 import { DocumentView } from "../nodes/DocumentView";
 import { LinkDocPreview } from "../nodes/LinkDocPreview";
 import { LinkEditor } from "./LinkEditor";
diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss
index 9ab3171d3..0f3eb14bc 100644
--- a/src/client/views/nodes/DocumentLinksButton.scss
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -2,7 +2,9 @@
 
 .documentLinksButton-wrapper {
     transform-origin: top left;
+    width: 100%;
 }
+
 .documentLinksButton-menu {
     width: 100%;
     height: 100%;
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index d2e6e8579..7f69adf6c 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -160,8 +160,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             endLink = endLinkView?.docView?._componentView?.getAnchor?.() || endLink;
             startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.() || startLink;
             const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink },
-                DocumentLinksButton.AnnotationId ? "hypothes.is annotation" :
-                    endLink.type === DocumentType.RTF ? "Commentary:Comments On" : "link", undefined, undefined, true);
+                DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : undefined, undefined, undefined, true);
 
             LinkManager.currentLink = linkDoc;
 
@@ -245,9 +244,9 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                 </div>
             </div>
             :
-            <div className="documentLinksButton-menu" ref={this._linkButton}>
+            <div className="documentLinksButton-menu" >
                 {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? //if the origin node is not this node
-                    <div className={"documentLinksButton-endLink"}
+                    <div className={"documentLinksButton-endLink"} ref={this._linkButton}
                         onPointerDown={DocumentLinksButton.StartLink && this.completeLink}
                         onClick={e => DocumentLinksButton.StartLink && DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View)}>
                         <FontAwesomeIcon className="documentdecorations-icon" icon="link" />
@@ -256,7 +255,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                 }
                 {
                     this.props.InMenu && this.props.StartLink ? //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again
-                        <div className={`documentLinksButton ${isActive ? `startLink` : ``}`} onPointerDown={isActive ? undefined : this.onLinkButtonDown} onClick={isActive ? this.clearLinks : this.onLinkClick}>
+                        <div className={`documentLinksButton ${isActive ? `startLink` : ``}`} ref={this._linkButton}
+                            onPointerDown={isActive ? undefined : this.onLinkButtonDown} onClick={isActive ? this.clearLinks : this.onLinkClick}>
                             <FontAwesomeIcon className="documentdecorations-icon" icon="link" />
                         </div>
                         :
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9d738180b..8570f6fff 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -663,7 +663,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             }
             if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.context) {
                 const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.() ?? this.props.Document;
-                de.complete.linkDocument = DocUtils.MakeLink({ doc: linkdrag.linkSourceDoc }, { doc: dropDoc }, "link", undefined, undefined, undefined, [de.x, de.y]);
+                de.complete.linkDocument = DocUtils.MakeLink({ doc: linkdrag.linkSourceDoc }, { doc: dropDoc }, undefined, undefined, undefined, undefined, [de.x, de.y]);
             }
         }
     }
@@ -674,7 +674,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const portalLink = this.allLinks.find(d => d.anchor1 === this.props.Document);
         if (!portalLink) {
             const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _fitWidth: true, title: StrCast(this.props.Document.title) + " [Portal]" });
-            DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to");
+            DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to:portal from");
         }
         this.Document.followLinkLocation = "inPlace";
         this.Document.followLinkZoom = true;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index aadad5ffa..5982d4d66 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -152,7 +152,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         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.layout = ImageBox.LayoutString("data");
         croppingProto.data = ObjectField.MakeCopy(this.rootDoc[this.fieldKey] as ObjectField);
         croppingProto["data-nativeWidth"] = anchw;
         croppingProto["data-nativeHeight"] = anchh;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index b556b0ef1..668c6c8fc 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -160,11 +160,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     public backPage = () => {
         this.Document._curPage = Math.max(1, (NumCast(this.Document._curPage) || 1) - 1);
         return true;
-    };
+    }
     public forwardPage = () => {
         this.Document._curPage = Math.min(NumCast(this.dataDoc[this.props.fieldKey + "-numPages"]), (NumCast(this.Document._curPage) || 1) + 1);
         return true;
-    };
+    }
     public gotoPage = (p: number) => this.Document._curPage = p;
 
     @undoBatch
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 00c03875b..427e05edb 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -275,7 +275,7 @@ export class RichTextRules {
                                     this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
                                 }
                                 const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: rawdocid.replace(/^:/, ""), _width: 500, _height: 500, }, docid);
-                                DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, "portal to", undefined);
+                                DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, "portal to:portal from", undefined);
 
                                 const fstate = this.TextBox.EditorView?.state;
                                 if (fstate && selection) {
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 5fe2a5ab1..9257cb75e 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -112,7 +112,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
         if (this.props.linkFrom) {
             const linkFrom = this.props.linkFrom();
             if (linkFrom) {
-                DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo }, "Link");
+                DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo });
             }
         }
     });
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 5e0cd67cc..83a2bc79b 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -14,7 +14,7 @@ import { normalize } from "path";
 import RouteSubscriber from "../RouteSubscriber";
 const imageDataUri = require('image-data-uri');
 import { SolrManager } from "./SearchManager";
-let fs = require('fs');
+const fs = require('fs');
 
 export enum Directory {
     parsed_files = "parsed_files",
@@ -265,7 +265,7 @@ export default class UploadManager extends ApiManager {
                 }
                 if (deleteFiles) {
                     const path = serverPathToFile(Directory.images, "");
-                    let regex = new RegExp(`${deleteFiles}.*`);
+                    const regex = new RegExp(`${deleteFiles}.*`);
                     fs.readdirSync(path).filter((f: any) => regex.test(f)).map((f: any) => fs.unlinkSync(path + f));
                 }
                 return imageDataUri.outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, origSuffix))).then((savedName: string) => {
-- 
cgit v1.2.3-70-g09d2


From 45062251a0ee91c5ece36141730a75dde03814dd Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 29 Apr 2022 16:35:05 -0400
Subject: fixed creating collection icons to capture pfs (and other things with
 canvases)

---
 src/client/util/CurrentUserUtils.ts                |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 30 ++++++++++++++++++----
 src/client/views/nodes/PDFBox.tsx                  |  1 +
 3 files changed, 27 insertions(+), 6 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 57313cecc..03c63e737 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -320,7 +320,7 @@ export class CurrentUserUtils {
             makeIconTemplate(DocumentType.COL, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})),
             makeIconTemplate(DocumentType.VID, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})),
             makeIconTemplate(DocumentType.BUTTON, "data", () => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, system: true, onClick: deiconifyScript() })),
-            // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {}), finalizeImageTemplate)
+            // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {}))
         ].filter(d => d).map(d => d!);
         if (!templateIconsDoc) {
             doc["template-icons"] = Docs.Create.TreeDocument(newIconsList, { title: "icon templates", _height: 75, system: true });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3f72052ae..452a6bfcf 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1422,12 +1422,32 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }));
     }
 
+    replaceCanvases = (oldDiv: HTMLElement, newDiv: HTMLElement) => {
+        if (oldDiv.childNodes) {
+            for (let i = 0; i < oldDiv.childNodes.length; i++) {
+                this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement);
+            }
+        }
+        if (oldDiv instanceof HTMLCanvasElement) {
+            const canvas = oldDiv;
+            const img = document.createElement('img'); // create a Image Element
+            img.src = canvas.toDataURL();   //image source
+            img.style.width = canvas.style.width;
+            img.style.height = canvas.style.height;
+            const newCan = newDiv as HTMLCanvasElement;
+            const parEle = newCan.parentElement as HTMLElement;
+            parEle.removeChild(newCan);
+            parEle.appendChild(img);
+        }
+    }
+
     updateIcon = () => {
-        this.props.docViewPath().lastElement().ContentDiv!.style.width = (this.layoutDoc[WidthSym]()).toString();
-        this.props.docViewPath().lastElement().ContentDiv!.style.height = (this.layoutDoc[HeightSym]()).toString();
-        const htmlString = this._mainCont && new XMLSerializer().serializeToString(this.props.docViewPath().lastElement().ContentDiv!);
-        this.props.docViewPath().lastElement().ContentDiv!.style.width = "";
-        this.props.docViewPath().lastElement().ContentDiv!.style.height = "";
+        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._mainCont && new XMLSerializer().serializeToString(newDiv);
         const nativeWidth = this.layoutDoc[WidthSym]();
         const nativeHeight = this.layoutDoc[HeightSym]();
 
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 668c6c8fc..f386c7bbd 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -290,6 +290,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
         funcs.push({ description: "Copy path", event: () => this.pdfUrl && Utils.CopyText(Utils.prepend("") + this.pdfUrl.url.pathname), icon: "expand-arrows-alt" });
+        funcs.push({ description: "update icon", event: () => this.pdfUrl && this.updateIcon(), icon: "expand-arrows-alt" });
         //funcs.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" });
         ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
     }
-- 
cgit v1.2.3-70-g09d2


From a6aec8759298fba41b486a973796662056f47da0 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 29 Apr 2022 17:05:10 -0400
Subject: changed the way labelBox splits words to allow .&- added icon for
 webBox

---
 src/client/util/CurrentUserUtils.ts                                 | 1 +
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 +-
 src/client/views/nodes/DocumentView.scss                            | 6 ++++--
 src/client/views/nodes/LabelBox.tsx                                 | 2 +-
 src/client/views/nodes/PDFBox.tsx                                   | 2 +-
 src/client/views/nodes/WebBoxRenderer.js                            | 2 +-
 6 files changed, 9 insertions(+), 6 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 03c63e737..9720343e1 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -315,6 +315,7 @@ export class CurrentUserUtils {
             makeIconTemplate(undefined, "title", () => labelBox({ _backgroundColor: "dimgray" })),
             makeIconTemplate(DocumentType.AUDIO, "title", () => labelBox({ _backgroundColor: "lightgreen" })),
             makeIconTemplate(DocumentType.PDF, "title", () => labelBox({ _backgroundColor: "pink" })),
+            makeIconTemplate(DocumentType.WEB, "title", () => labelBox({ _backgroundColor: "brown" })),
             makeIconTemplate(DocumentType.RTF, "text", () => labelBox({ _showTitle: "creationDate" })),
             makeIconTemplate(DocumentType.IMG, "data", () => imageBox("", { _height: undefined, })),
             makeIconTemplate(DocumentType.COL, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})),
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 452a6bfcf..5d1e1892c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1454,7 +1454,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         CreateImage(
             "",
             document.styleSheets,
-            htmlString?.replace(/docView-hack/g, 'documentView-hack'),
+            htmlString,
             nativeWidth,
             nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(),
             NumCast(this.layoutDoc._scrollTop)
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 5c5d2df10..6a1bfa406 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -3,12 +3,14 @@
 .documentView-effectsWrapper {
     border-radius: inherit;
 }
+
+// documentViews have a docView-hack tag which is replaced by this tag when capturing bitmaps (when the dom is converted to an html string)
 .documentView-hack {
-    display: inline;
+    display: inline; //  this swap is needed because for some reason when capturing bitmaps, things don't appear unless dispay:inline is explicitly set.
 }
 
 .documentView-customBorder {
-    width:100%;
+    width: 100%;
     height: 100%;
     position: absolute;
     top: 0;
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index dfff59005..1a28fc318 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -132,7 +132,7 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro
                     whiteSpace: this.layoutDoc._singleLine ? "pre" : "pre-wrap"
                 }} >
                     <span ref={action((r: any) => this.fitTextToBox(r))}>
-                        {label.startsWith("#") ? (null) : label}
+                        {label.startsWith("#") ? (null) : label.replace(/([\.\-&])/g, "$1 ")}
                     </span>
                 </div>
                 <div className="labelBox-fieldKeyParams" >
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index f386c7bbd..69662d53a 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -87,7 +87,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         CreateImage(
             "",
             document.styleSheets,
-            htmlString?.replace(/docView-hack/g, 'documentView-hack'),
+            htmlString,
             nativeWidth,
             nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(),
             NumCast(this.layoutDoc._scrollTop) * this.props.PanelHeight() / NumCast(this.rootDoc[this.fieldKey + "-nativeHeight"])
diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js
index 08a5746d1..d2b701cf9 100644
--- a/src/client/views/nodes/WebBoxRenderer.js
+++ b/src/client/views/nodes/WebBoxRenderer.js
@@ -312,7 +312,7 @@ var ForeignHtmlRenderer = function (styleSheets) {
 
 
 export function CreateImage(webUrl, styleSheets, html, width, height, scroll) {
-    const val = (new ForeignHtmlRenderer(styleSheets)).renderToBase64Png(webUrl, html.replace(/\n/g, "").replace(/<script((?!\/script).)*<\/script>/g, ""), 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);
     return val;
 }
 
-- 
cgit v1.2.3-70-g09d2


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/util/DocumentManager.ts                 | 42 +++++++++++++++-------
 .../util/Import & Export/DirectoryImportBox.tsx    |  2 +-
 src/client/util/LinkManager.ts                     | 11 +++---
 src/client/util/SharingManager.tsx                 | 30 ++++++++--------
 src/client/views/DocumentDecorations.tsx           | 13 ++++---
 src/client/views/MainView.tsx                      |  2 +-
 src/client/views/collections/TabDocView.tsx        |  2 +-
 src/client/views/collections/TreeView.tsx          |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../collectionLinear/CollectionLinearView.tsx      |  2 +-
 .../collectionSchema/CollectionSchemaCells.tsx     |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  3 +-
 src/client/views/nodes/LinkDescriptionPopup.tsx    |  1 +
 src/client/views/nodes/PDFBox.tsx                  |  1 +
 src/client/views/nodes/WebBox.scss                 |  3 ++
 src/client/views/nodes/WebBox.tsx                  | 11 +++---
 src/client/views/nodes/trails/PresBox.tsx          |  4 +--
 src/client/views/search/SearchBox.tsx              |  2 +-
 18 files changed, 84 insertions(+), 51 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index b6c28d2fe..aeddc3249 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -156,7 +156,7 @@ export class DocumentManager {
         targetDoc: Doc,        // document to display
         willZoom: boolean,     // whether to zoom doc to take up most of screen
         createViewFunc = DocumentManager.addView, // how to create a view of the doc if it doesn't exist
-        docContext?: Doc,  // context to load that should contain the target
+        docContext: Doc[],  // context to load that should contain the target
         linkDoc?: Doc,   // link that's being followed
         closeContextIfNotFound: boolean = false, // after opening a context where the document should be, this determines whether the context should be closed if the Doc isn't actually there
         originatingDoc: Opt<Doc> = undefined, // doc that initiated the display of the target odoc
@@ -190,31 +190,40 @@ export class DocumentManager {
             finished?.();
         };
         const annoContainerView = (!wasHidden || resolvedTarget !== annotatedDoc) && annotatedDoc && getFirstDocView(annotatedDoc);
-        const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
-        const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext : undefined;
+        const contextDocs = docContext.length ? await DocListCastAsync(docContext[0].data) : undefined;
+        const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext.lastElement() : undefined;
         const targetDocContext = contextDoc || annotatedDoc;
         const targetDocContextView = (targetDocContext && getFirstDocView(targetDocContext)) ||
             (wasHidden && annoContainerView);// if we have an annotation container and the target was hidden, then try again because we just un-hid the document above
         const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView;
-        if ((!docView && targetDoc.type !== DocumentType.MARKER) && annoContainerView) {
+        if (annoContainerView) {
             if (annoContainerView.props.Document.layoutKey === "layout_icon") {
-                annoContainerView.iconify(() => this.jumpToDocument(
-                    targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc,
-                    finished, originalTarget, noSelect, presZoom));
+                annoContainerView.iconify(() => annoContainerView.focus(targetDoc, {
+                    originalTarget, willZoom, scale: presZoom, afterFocus: (didFocus: boolean) =>
+                        new Promise<ViewAdjustment>(res => {
+                            focusAndFinish(true);
+                            res(ViewAdjustment.doNothing);
+                        })
+                }));
                 return;
-            } else {
+            } else if (!docView && targetDoc.type !== DocumentType.MARKER) {
                 annoContainerView.focus(targetDoc);  // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below
             }
         }
         if (focusView) {
             !noSelect && Doc.linkFollowHighlight(focusView.rootDoc); //TODO:glr make this a setting in PresBox
-            focusView.focus(targetDoc, {
+            const doFocus = (forceDidFocus: boolean) => focusView.focus(originalTarget ?? targetDoc, {
                 originalTarget, willZoom, scale: presZoom, afterFocus: (didFocus: boolean) =>
                     new Promise<ViewAdjustment>(res => {
-                        focusAndFinish(didFocus);
+                        focusAndFinish(forceDidFocus || didFocus);
                         res(ViewAdjustment.doNothing);
                     })
             });
+            if (focusView.props.Document.layoutKey === "layout_icon") {
+                focusView.iconify(() => doFocus(true));
+            } else {
+                doFocus(false);
+            }
         } else {
             if (!targetDocContext) { // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default
                 createViewFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc)));
@@ -228,7 +237,7 @@ export class DocumentManager {
                             targetDocContext._viewTransition = undefined;
                             if (targetDocContext.layoutKey === "layout_icon") {
                                 targetDocContextView.iconify(() => this.jumpToDocument(
-                                    targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc,
+                                    resolvedTarget ?? targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc,
                                     finished, originalTarget, noSelect, presZoom));
                             }
                             return ViewAdjustment.doNothing;
@@ -263,7 +272,16 @@ export class DocumentManager {
                         };
                         setTimeout(() => findView(0), 0);
                     }
-                } else {  // there's no context view so we need to create one first and try again when that finishes
+                } else {
+                    if (docContext.length && docContext[0]?.layoutKey === "layout_icon") {
+                        const docContextView = this.getFirstDocumentView(docContext[0]);
+                        if (docContextView) {
+                            return docContextView.iconify(() => this.jumpToDocument(
+                                targetDoc, willZoom, createViewFunc, docContext.slice(1, docContext.length), linkDoc, closeContextIfNotFound, originatingDoc,
+                                finished, originalTarget, noSelect, presZoom));
+                        }
+                    }
+                    // there's no context view so we need to create one first and try again when that finishes
                     const finishFunc = () => this.jumpToDocument(targetDoc, true, createViewFunc, docContext, linkDoc, true /* if we don't find the target, we want to get rid of the context just created */, undefined, finished, originalTarget);
                     createViewFunc(targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target
                         finishFunc);
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 39e9251a5..37571ae01 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -161,7 +161,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
             await GooglePhotos.Export.CollectionToAlbum({ collection: importContainer });
             Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer);
             !this.persistent && this.props.removeDocument && this.props.removeDocument(doc);
-            DocumentManager.Instance.jumpToDocument(importContainer, true);
+            DocumentManager.Instance.jumpToDocument(importContainer, true, undefined, []);
         }
 
         runInAction(() => {
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 9445533dc..b28662a57 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,3 +1,4 @@
+import { validationResult } from "express-validator/check";
 import { action, observable, observe } from "mobx";
 import { computedFn } from "mobx-utils";
 import { DirectLinksSym, Doc, DocListCast, Field, Opt } from "../../fields/Doc";
@@ -241,10 +242,12 @@ export class LinkManager {
                     } else {
                         const containerAnnoDoc = Cast(target.annotationOn, Doc, null);
                         const containerDoc = containerAnnoDoc || target;
-                        const containerDocContext = Cast(containerDoc?.context, Doc, null);
-                        const targetContext = LightboxView.LightboxDoc ? containerAnnoDoc || containerDocContext : containerDocContext;
-                        const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
-                        DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "lightbox"), finished), targetNavContext, linkDoc, undefined, sourceDoc, allFinished);
+                        var containerDocContext = containerDoc?.context ? [Cast(containerDoc?.context, Doc, null)] : [] as Doc[];
+                        while (containerDocContext.length && !DocumentManager.Instance.getDocumentView(containerDocContext[0]) && containerDocContext[0].context) {
+                            containerDocContext = [Cast(containerDocContext[0].context, Doc, null), ...containerDocContext];
+                        }
+                        const targetContexts = LightboxView.LightboxDoc ? [containerAnnoDoc || containerDocContext[0]] : containerDocContext;
+                        DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "lightbox"), finished), targetContexts, linkDoc, undefined, sourceDoc, allFinished);
                     }
                 } else {
                     allFinished();
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 6d7f7e8df..11450309a 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -370,11 +370,11 @@ export class SharingManager extends React.Component<{}> {
         if (!uniform) dropdownValues.unshift("-multiple-");
         if (override) dropdownValues.unshift("None");
         return dropdownValues.filter(permission => !Doc.UserDoc().noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission =>
-            (
-                <option key={permission} value={permission}>
-                    {permission}
-                </option>
-            )
+        (
+            <option key={permission} value={permission}>
+                {permission}
+            </option>
+        )
         );
     }
 
@@ -388,7 +388,7 @@ export class SharingManager extends React.Component<{}> {
                 onClick={() => {
                     let context: Opt<CollectionView>;
                     if (this.targetDoc && this.targetDocView && docs.length === 1 && (context = this.targetDocView.props.ContainingCollectionView)) {
-                        DocumentManager.Instance.jumpToDocument(this.targetDoc, true, undefined, context.props.Document);
+                        DocumentManager.Instance.jumpToDocument(this.targetDoc, true, undefined, [context.props.Document]);
                     }
                 }}
                 onPointerEnter={action(() => {
@@ -548,10 +548,10 @@ export class SharingManager extends React.Component<{}> {
                                 {this.sharingOptions(uniform)}
                             </select>
                         ) : (
-                                <div className={"permissions-dropdown"}>
-                                    {permissions}
-                                </div>
-                            )}
+                            <div className={"permissions-dropdown"}>
+                                {permissions}
+                            </div>
+                        )}
                     </div>
                 </div>
             );
@@ -572,7 +572,7 @@ export class SharingManager extends React.Component<{}> {
                         <div className="edit-actions">
                             <div className={"permissions-dropdown"}>
                                 Owner
-                        </div>
+                            </div>
                         </div>
                     </div>
                 ) : null,
@@ -622,10 +622,10 @@ export class SharingManager extends React.Component<{}> {
                                 {this.sharingOptions(uniform, group.title === "Override")}
                             </select>
                         ) : (
-                                <div className={"permissions-dropdown"}>
-                                    {permissions}
-                                </div>
-                            )}
+                            <div className={"permissions-dropdown"}>
+                                {permissions}
+                            </div>
+                        )}
                     </div>
                 </div>
             );
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index eb2bcc18c..936946df0 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -113,7 +113,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         }
     }
 
-    titleEntered = (e: React.KeyboardEvent) => e.key === "Enter" && (e.target as any).blur();
+    titleEntered = (e: React.KeyboardEvent) => {
+        if (e.key === "Enter") {
+            e.stopPropagation();
+            (e.target as any).blur();
+        }
+    }
 
     @action onTitleDown = (e: React.PointerEvent): void => {
         setupMoveUpEvents(this, e, e => this.onBackgroundMove(true, e), (e) => { }, action((e) => {
@@ -288,7 +293,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         if (fixedAspect && (resizeHdl === "documentDecorations-bottomRightResizer" || resizeHdl === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles
             const project = (p: number[], a: number[], b: number[]) => {
                 const atob = [b[0] - a[0], b[1] - a[1]];
-                const atop = [p[0] - a[0], p[1] - a[1]]; first
+                const atop = [p[0] - a[0], p[1] - a[1]];
                 const len = atob[0] * atob[0] + atob[1] * atob[1];
                 let dot = atop[0] * atob[0] + atop[1] * atob[1];
                 const t = dot / len;
@@ -501,7 +506,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                     value={this._accumulatedTitle}
                     onBlur={e => this.titleBlur()}
                     onChange={action(e => this._accumulatedTitle = e.target.value)}
-                    onKeyPress={this.titleEntered} /> :
+                    onKeyDown={this.titleEntered} /> :
                 <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} >
                     <span className={`documentDecorations-titleSpan${colorScheme}`}>{`${this.selectionTitle}`}</span>
                 </div>;
@@ -521,7 +526,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         const useRotation = seldoc.ComponentView instanceof InkingStroke || seldoc.ComponentView instanceof ImageBox;
         const resizerScheme = colorScheme ? "documentDecorations-resizer" + colorScheme : "";
 
-        const rotation = NumCast(seldoc.rootDoc._jitterRotation)
+        const rotation = NumCast(seldoc.rootDoc._jitterRotation);
         const rdeg = rotation / 180 * Math.PI;
 
         return (<div className={`documentDecorations${colorScheme}`}>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index c794c73db..db51c54f8 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -133,7 +133,7 @@ export class MainView extends React.Component {
         window.addEventListener("paste", KeyManager.Instance.paste as any);
         document.addEventListener("dash", (e: any) => {  // event used by chrome plugin to tell Dash which document to focus on
             const id = FormattedTextBox.GetDocFromUrl(e.detail);
-            DocServer.GetRefField(id).then(doc => (doc instanceof Doc) ? DocumentManager.Instance.jumpToDocument(doc, false, undefined) : (null));
+            DocServer.GetRefField(id).then(doc => (doc instanceof Doc) ? DocumentManager.Instance.jumpToDocument(doc, false, undefined, []) : (null));
         });
         document.addEventListener("linkAnnotationToDash", Hypothesis.linkListener);
         this.initEventListeners();
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 78b125e07..8dfc7edc7 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -256,7 +256,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 }
                 PresBox.Instance?._selectedArray.clear();
                 pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array
-                DocumentManager.Instance.jumpToDocument(doc, false, undefined);
+                DocumentManager.Instance.jumpToDocument(doc, false, undefined, []);
                 batch.end();
             });
         }
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 72480b094..164021358 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -72,7 +72,7 @@ export enum TreeSort {
     Down = "down",
     Zindex = "z",
     None = "none"
-};
+}
 /**
  * Renders a treeView of a collection of documents
  * 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5d1e1892c..5b3a7db41 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1423,7 +1423,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     replaceCanvases = (oldDiv: HTMLElement, newDiv: HTMLElement) => {
-        if (oldDiv.childNodes) {
+        if (oldDiv.childNodes && newDiv) {
             for (let i = 0; i < oldDiv.childNodes.length; i++) {
                 this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement);
             }
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index e3d4f86c1..c615bfb8e 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -231,7 +231,7 @@ export class CollectionLinearView extends CollectionSubView() {
                         <span className="bottomPopup-text">
                             Currently playing:
                             {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) =>
-                                <span className="audio-title" onPointerDown={() => DocumentManager.Instance.jumpToDocument(clip, true)}>
+                                <span className="audio-title" onPointerDown={() => DocumentManager.Instance.jumpToDocument(clip, true, undefined, [])}>
                                     {clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? "" : ",")}
                                 </span>)}
                         </span>
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
index c2bb3b3ac..adcd9e1e3 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
@@ -199,7 +199,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
             const aliasdoc = await SearchUtil.GetAliasesOfDocument(this._rowDataDoc);
             const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null);
             // Jump to the this document
-            DocumentManager.Instance.jumpToDocument(this._rowDoc, false, emptyFunction, targetContext,
+            DocumentManager.Instance.jumpToDocument(this._rowDoc, false, emptyFunction, targetContext ? [targetContext] : [],
                 undefined, undefined, undefined, () => this.props.setPreviewDoc(this._rowDoc));
         }
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8570f6fff..02ea87103 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -663,7 +663,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             }
             if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.context) {
                 const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.() ?? this.props.Document;
-                de.complete.linkDocument = DocUtils.MakeLink({ doc: linkdrag.linkSourceDoc }, { doc: dropDoc }, undefined, undefined, undefined, undefined, [de.x, de.y]);
+                de.complete.linkDocument = DocUtils.MakeLink({ doc: linkdrag.linkSourceDoc }, { doc: dropDoc }, undefined, undefined, undefined, undefined, [de.x, de.y - 50]);
             }
         }
     }
@@ -857,6 +857,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
     @observable _retryThumb = 1;
     thumbShown = () => !this.props.isSelected() && LightboxView.LightboxDoc !== this.rootDoc && this.thumb &&
+        !Doc.AreProtosEqual(DocumentLinksButton.StartLink, this.rootDoc) &&
         !this._componentView?.isAnyChildContentActive?.() ? true : false
     @computed get contents() {
         TraceMobx();
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index a9d33f161..ccac66996 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -54,6 +54,7 @@ export class LinkDescriptionPopup extends React.Component<{}> {
                 top: LinkDescriptionPopup.popupY ? LinkDescriptionPopup.popupY : 350,
             }}>
             <input className="linkDescriptionPopup-input"
+                onKeyDown={e => e.stopPropagation()}
                 onKeyPress={e => e.key === "Enter" && this.onDismiss(true)}
                 placeholder={"(Optional) Enter link description..."}
                 onChange={(e) => this.descriptionChanged(e)}>
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();
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index b037e7220..d8dd074a5 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -156,6 +156,9 @@
             position: absolute;
             top: 0;
             left: 0;
+            cursor: text;
+            padding: 15px;
+            height: 100%
         }
 
         .webBox-cont {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 80961c92f..f441e5d36 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -207,8 +207,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }
 
     @action
-    createTextAnnotation = (sel: Selection, selRange: Range) => {
-        if (this._mainCont.current) {
+    createTextAnnotation = (sel: Selection, selRange: Range | undefined) => {
+        if (this._mainCont.current && selRange) {
             const clientRects = selRange.getClientRects();
             for (let i = 0; i < clientRects.length; i++) {
                 const rect = clientRects.item(i);
@@ -235,7 +235,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     menuControls = () => this.urlEditor; // controls to be added to the top bar when a document of this type is selected
 
     scrollFocus = (doc: Doc, smooth: boolean) => {
-        this.props.select(true);
+        this.props.select(false); // hack to preven the webBox from being replaced with a thumbnail that currently doesn't show the the anchors
         if (StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl), !smooth);
         if (DocListCast(this.props.Document[this.fieldKey + "-sidebar"]).includes(doc) && !this.SidebarShown) {
             this.toggleSidebar(!smooth);
@@ -252,7 +252,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             }
         }
         this._initialScroll = NumCast(this.layoutDoc._scrollTop);
-        return 0;
+        return undefined;
     }
 
     getAnchor = () => {
@@ -279,7 +279,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             const scale = (this.props.scaling?.() || 1) * mainContBounds.scale;
             const sel = this._iframe.contentWindow.getSelection();
             if (sel) {
-                this._textAnnotationCreator = () => this.createTextAnnotation(sel, sel.getRangeAt(0));
+                this._textAnnotationCreator = () => this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined);
                 AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX,
                     e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale);
             }
@@ -650,6 +650,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @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;
         return <div className={"webBox-cont" + (interactive ? "-interactive" : "")}
+            onKeyDown={e => e.stopPropagation()}
             style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) || `100%` : "100%", }}>
             {this.urlContent}
         </div>;
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 30ad43562..820d7ef96 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -389,11 +389,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             LightboxView.SetLightboxDoc(targetDoc);
         } else if (curDoc.presMovement === PresMovement.Pan && targetDoc) {
             LightboxView.SetLightboxDoc(undefined);
-            await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right
+            await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, [srcContext], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right
         } else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) {
             LightboxView.SetLightboxDoc(undefined);
             //awaiting jump so that new scale can be found, since jumping is async     
-            await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right    
+            await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, [srcContext], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right    
         }
         // After navigating to the document, if it is added as a presPinView then it will
         // adjust the pan and scale to that of the pinView when it was added.
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 9257cb75e..3d36efbc4 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -365,7 +365,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
      * or opening it in a new tab.
      */
     selectElement = async (doc: Doc, finishFunc: () => void) => {
-        await DocumentManager.Instance.jumpToDocument(doc, true, undefined, undefined, undefined, undefined, undefined, finishFunc);
+        await DocumentManager.Instance.jumpToDocument(doc, true, undefined, [], undefined, undefined, undefined, finishFunc);
     }
 
     /**
-- 
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


From ab5e48a2340f06628fc22d1267d081de9dbc572f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 9 Jun 2022 23:24:34 -0400
Subject: fixed up creating dashboard icons to work with pdfs in freeformviews
 with a grid.  no idea why having a grid breaks thrings.

---
 src/client/util/CurrentUserUtils.ts                |  2 +-
 src/client/views/DashboardView.tsx                 |  4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 53 ++++++++++++++--------
 src/client/views/nodes/PDFBox.tsx                  | 44 +++++++-----------
 4 files changed, 53 insertions(+), 50 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 3b1009532..9a01f3e5d 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1194,7 +1194,7 @@ export class CurrentUserUtils {
                 docView.layoutDoc[Id] + "-icon" + (new Date()).getTime(),
                 content,
                 _width, _height,
-                _width, _height, 0,
+                _width, _height, 0, 1, true, docView.layoutDoc[Id] + "-icon", 
                 (iconFile, _nativeWidth, _nativeHeight) => {
                     const img = Docs.Create.ImageDocument(new ImageField(iconFile), { title: docView.rootDoc.title+"-icon", _width, _height, _nativeWidth, _nativeHeight});
                     const proto = Cast(img.proto, Doc, null)!;
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index b08151c0f..e003968d6 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -36,9 +36,7 @@ export class DashboardView extends React.Component {
                   </div>
                   <div className="all-dashboards">
                         {myDashboards.map((dashboard) => {
-                              const url = ImageCast((dashboard.thumb as Doc)?.data)?.url;
-                              const ext = url ? extname(url.href):"";
-                              const href = url?.href.replace(ext, "_m"+ ext); // need to choose which resolution image to show.  options are _s, _m, _l, _o   (small/medium/large/original)
+                              const href = ImageCast((dashboard.thumb as Doc)?.data)?.url.href;
                               return <div className="dashboard-container" key={dashboard[Id]} onClick={e => this.clickDashboard(e, dashboard)}>
                                     <img src={href ?? "https://media.istockphoto.com/photos/hot-air-balloons-flying-over-the-botan-canyon-in-turkey-picture-id1297349747?b=1&k=20&m=1297349747&s=170667a&w=0&h=oH31fJty_4xWl_JQ4OIQWZKP8C6ji9Mz7L4XmEnbqRU="}></img>
                                     <div className="title"> {StrCast(dashboard.title)} </div>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ef3a896e1..99f74b8a2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1500,15 +1500,22 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             }
         }
         if (oldDiv instanceof HTMLCanvasElement) {
-            const canvas = oldDiv;
-            const img = document.createElement('img'); // create a Image Element
-            img.src = canvas.toDataURL();   //image source
-            img.style.width = canvas.style.width;
-            img.style.height = canvas.style.height;
-            const newCan = newDiv as HTMLCanvasElement;
-            const parEle = newCan.parentElement as HTMLElement;
-            parEle.removeChild(newCan);
-            parEle.appendChild(img);
+            if (oldDiv.className === "collectionFreeFormView-grid") {
+                const newCan = newDiv as HTMLCanvasElement;
+                const parEle = newCan.parentElement as HTMLElement;
+                parEle.removeChild(newCan);
+                parEle.appendChild(document.createElement('div'))
+            } else {
+                const canvas = oldDiv;
+                const img = document.createElement('img'); // create a Image Element
+                img.src = canvas.toDataURL();   //image source
+                img.style.width = canvas.style.width;
+                img.style.height = canvas.style.height;
+                const newCan = newDiv as HTMLCanvasElement;
+                const parEle = newCan.parentElement as HTMLElement;
+                parEle.removeChild(newCan);
+                parEle.appendChild(img);
+            }
         }
     }
 
@@ -1516,15 +1523,23 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this.layoutDoc[Id] + "-icon" + (new Date()).getTime(),
         this.props.docViewPath().lastElement().ContentDiv!,
         this.layoutDoc[WidthSym](), this.layoutDoc[HeightSym](), 
-        this.props.PanelWidth(), this.props.PanelHeight(), 0, 
+        this.props.PanelWidth(), this.props.PanelHeight(), 0, 1, false, "",
         (iconFile, nativeWidth, nativeHeight) => {
-                    this.dataDoc.icon = new ImageField(iconFile);
-                    this.dataDoc["icon-nativeWidth"] = nativeWidth;
-                    this.dataDoc["icon-nativeHeight"] = nativeHeight;
+            this.dataDoc.icon = new ImageField(iconFile);
+            this.dataDoc["icon-nativeWidth"] = nativeWidth;
+            this.dataDoc["icon-nativeHeight"] = nativeHeight;
         });
 
-    public static UpdateIcon(filename:string, docViewContent:HTMLElement, width: number, height: number, 
-        panelWidth:number, panelHeight: number, scrollTop:number, cb:(iconFile:string, nativeWidth:number, nativeHeight:number) => any) {
+    public static UpdateIcon(
+        filename:string, docViewContent:HTMLElement, 
+        width: number, height: number, 
+        panelWidth:number, panelHeight: number, 
+        scrollTop:number, 
+        realNativeHeight: number,
+        noSuffix: boolean,
+        replaceRootFilename: string| undefined,
+        cb:(iconFile:string, nativeWidth:number, nativeHeight:number) => any) 
+    {
         const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
         newDiv.style.width = width.toString();
         newDiv.style.height = height.toString();
@@ -1538,10 +1553,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             htmlString,
             nativeWidth,
             nativeWidth * panelHeight / panelWidth,
-            scrollTop
+            scrollTop * panelHeight  / realNativeHeight
         ).then
             (async (data_url: any) => {
-                const returnedFilename = await VideoBox.convertDataUri(data_url, filename);
+                const returnedFilename = await VideoBox.convertDataUri(data_url, filename, noSuffix, replaceRootFilename);
                 cb(returnedFilename as string, nativeWidth, nativeHeight);
             })
             .catch(function (error: any) {
@@ -1771,7 +1786,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             isAnnotationOverlay={this.isAnnotationOverlay}>
             <div className="marqueeView-div" ref={this._marqueeRef} style={{ opacity: this.props.dontRenderDocuments ? 0 : undefined }}>
                 {this.layoutDoc._backgroundGridShow ?
-                    <CollectionFreeFormBackgroundGrid
+                    <div><CollectionFreeFormBackgroundGrid  // bcz : UGHH  don't know why, but if we don't wrap in a div, then PDF's don't render whenn taking snapshot of a dashboard and the background grid is on!!?
                         PanelWidth={this.props.PanelWidth}
                         PanelHeight={this.props.PanelHeight}
                         panX={this.panX}
@@ -1781,7 +1796,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         isAnnotationOverlay={this.isAnnotationOverlay}
                         cachedCenteringShiftX={this.cachedCenteringShiftX}
                         cachedCenteringShiftY={this.cachedCenteringShiftY}
-                    /> : (null)}
+                    /></div> : (null)}
                 <CollectionFreeFormViewPannableContents
                     isAnnotationOverlay={this.isAnnotationOverlay}
                     isAnnotationOverlayScrollable={this.props.isAnnotationOverlayScrollable}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index f2ca6c96e..4eb07fd8d 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -26,6 +26,7 @@ import { ImageBox } from './ImageBox';
 import "./PDFBox.scss";
 import { VideoBox } from './VideoBox';
 import React = require("react");
+import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 
 @observer
 export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
@@ -147,34 +148,23 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }
 
     updateIcon = () => {
-        return; // currently we render pdf icons as text labels
+        // 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();
-        newDiv.style.height = (this.layoutDoc[HeightSym]()).toString();
-        this.replaceCanvases(docViewContent, newDiv);
-        const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv);
-        const nativeWidth = this.layoutDoc[WidthSym]();
-        const nativeHeight = this.layoutDoc[HeightSym]();
-
-        CreateImage(
-            "",
-            document.styleSheets,
-            htmlString,
-            nativeWidth,
-            nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(),
-            NumCast(this.layoutDoc._scrollTop) * this.props.PanelHeight() / NumCast(this.rootDoc[this.fieldKey + "-nativeHeight"])
-        ).then
-            ((data_url: any) => {
-                VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true, this.layoutDoc[Id] + "-icon").then(
-                    returnedfilename => setTimeout(action(() => {
-                        this.dataDoc.icon = new ImageField(returnedfilename);
-                        this.dataDoc["icon-nativeWidth"] = nativeWidth;
-                        this.dataDoc["icon-nativeHeight"] = nativeHeight;
-                    }), 500));
-            })
-            .catch(function (error: any) {
-                console.error('oops, something went wrong!', error);
+        const filename = this.layoutDoc[Id] + "-icon" + (new Date()).getTime();
+        this._pdfViewer?._mainCont.current && CollectionFreeFormView.UpdateIcon(
+            filename, docViewContent, 
+            this.layoutDoc[WidthSym](), this.layoutDoc[HeightSym](), 
+            this.props.PanelWidth(), this.props.PanelHeight(),  
+            NumCast(this.layoutDoc._scrollTop),
+            NumCast(this.rootDoc[this.fieldKey + "-nativeHeight"], 1), 
+            true,  
+            this.layoutDoc[Id] + "-icon",
+            (iconFile:string, nativeWidth:number, nativeHeight:number) => {
+                setTimeout(() => {
+                    this.dataDoc.icon = new ImageField(iconFile);
+                    this.dataDoc["icon-nativeWidth"] = nativeWidth;
+                    this.dataDoc["icon-nativeHeight"] = nativeHeight;
+                }, 500);
             });
     }
 
-- 
cgit v1.2.3-70-g09d2

