From 513dcaa2d8c86f1cb5236ce89062cace6f418d1b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 10 Aug 2022 14:52:04 -0400
Subject: cleaned up pinning documents with an activeFrame index (and special
 case of isInkMask).  removed text-color from pres boxes which was breaking
 opacity when docs had an appearFrame > 0.

---
 src/client/views/nodes/DocumentView.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f87581875..f9ef85595 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1640,8 +1640,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     render() {
         TraceMobx();
-        const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
-        const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
+        const xshift = () => (this.props.Document.isInkMask && !this.props.LayoutTemplateString && !this.props.LayoutTemplate?.() ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
+        const yshift = () => (this.props.Document.isInkMask && !this.props.LayoutTemplateString && !this.props.LayoutTemplate?.() ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
         const isPresTreeElement: boolean = this.props.treeViewDoc?.type === DocumentType.PRES;
         const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
         return (
-- 
cgit v1.2.3-70-g09d2


From 081632757af3c2ec2e4482c1e5fe710c8ee6cad8 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 11 Aug 2022 19:01:37 -0400
Subject: added better ui and backend support for mask ink strokes. added frame
 #'s ui for seeing current animation frame.

---
 src/client/documents/Documents.ts                  | 19 ++++++++--
 src/client/util/CurrentUserUtils.ts                |  6 ++--
 src/client/util/DragManager.ts                     | 14 ++++----
 src/client/views/InkingStroke.tsx                  | 28 +++++++++++----
 src/client/views/MainView.tsx                      |  1 +
 src/client/views/collections/CollectionMenu.tsx    |  2 +-
 src/client/views/collections/TabDocView.tsx        | 10 +++---
 .../CollectionFreeFormLayoutEngines.tsx            |  1 +
 .../collectionFreeForm/CollectionFreeFormView.scss | 23 +++++++++----
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  9 +++--
 src/client/views/global/globalCssVariables.scss    | 13 +++----
 .../views/global/globalCssVariables.scss.d.ts      |  6 ++--
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  7 ++--
 src/client/views/nodes/DocumentView.tsx            |  6 ++--
 src/client/views/nodes/button/FontIconBox.tsx      | 34 +++++++++++++-----
 src/client/views/nodes/trails/PresBox.tsx          | 36 +++++++++++--------
 src/fields/InkField.ts                             | 40 +++++++++++-----------
 17 files changed, 162 insertions(+), 93 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c7ea04839..cf9ed43e1 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -31,7 +31,7 @@ import { CollectionView } from '../views/collections/CollectionView';
 import { ContextMenu } from '../views/ContextMenu';
 import { ContextMenuProps } from '../views/ContextMenuItem';
 import { DFLT_IMAGE_NATIVE_DIM } from '../views/global/globalCssVariables.scss';
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke } from '../views/InkingStroke';
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke';
 import { AudioBox } from '../views/nodes/AudioBox';
 import { FontIconBox } from '../views/nodes/button/FontIconBox';
 import { ColorBox } from '../views/nodes/ColorBox';
@@ -918,9 +918,22 @@ export namespace Docs {
             return linkDoc;
         }
 
-        export function InkDocument(color: string, tool: string, strokeWidth: number, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: PointData[], options: DocumentOptions = {}) {
+        export function InkDocument(
+            color: string,
+            tool: string,
+            strokeWidth: number,
+            strokeBezier: string,
+            fillColor: string,
+            arrowStart: string,
+            arrowEnd: string,
+            dash: string,
+            points: PointData[],
+            isInkMask: boolean,
+            options: DocumentOptions = {}
+        ) {
             const I = new Doc();
             I[Initializing] = true;
+            I.isInkMask = isInkMask;
             I.type = DocumentType.INK;
             I.layout = InkingStroke.LayoutString('data');
             I.color = color;
@@ -1424,7 +1437,7 @@ export namespace DocUtils {
             created = Docs.Create.RecordingDocument(field.url.href, resolved);
             layout = RecordingBox.LayoutString;
         } else if (field instanceof InkField) {
-            created = Docs.Create.InkDocument(ActiveInkColor(), Doc.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), field.inkData, resolved);
+            created = Docs.Create.InkDocument(ActiveInkColor(), Doc.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), field.inkData, ActiveIsInkMask(), resolved);
             layout = InkingStroke.LayoutString;
         } else if (field instanceof List && field[0] instanceof Doc) {
             created = Docs.Create.StackingDocument(DocListCast(field), resolved);
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index d19874720..dcf4a71c8 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -645,8 +645,9 @@ export class CurrentUserUtils {
             // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveTool("highlighter")'} },
             { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle",                      scripts: {onClick:'{ return setActiveTool("circle", _readOnly_);}'} },
             // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveTool("square")' },
-            { title: "Line",   toolTip: "Line (Ctrl+Shift+L)",   btnType: ButtonType.ToggleButton, icon: "minus",                       scripts: {onClick: '{ return setActiveTool("line", _readOnly_);}' }},
-            { title: "Fill",   toolTip: "Fill color",            btnType: ButtonType.ColorButton,  icon: "fill-drip",ignoreClick: true, scripts: {script: "{ return setFillColor(value, _readOnly_);}"} },
+            { title: "Line",   toolTip: "Line (Ctrl+Shift+L)",   btnType: ButtonType.ToggleButton, icon: "minus",                       scripts: {onClick:'{ return setActiveTool("line", _readOnly_);}' }},
+            { title: "Mask",   toolTip: "Mask",                  btnType: ButtonType.ToggleButton, icon: "user-circle",                 scripts: {onClick:'{ return setIsInkMask(_readOnly_);}'} },
+            { title: "Fill",   toolTip: "Fill color",            btnType: ButtonType.ColorButton,  icon: "fill-drip",ignoreClick: true, scripts: {script: '{ return setFillColor(value, _readOnly_);}'} },
             { title: "Width",  toolTip: "Stroke width",          btnType: ButtonType.NumberButton,                   ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'}, numBtnType: NumButtonType.Slider, numBtnMin: 1},
             { title: "Color",  toolTip: "Stroke color",          btnType: ButtonType.ColorButton,  icon: "pen",      ignoreClick: true, scripts: {script: '{ return setStrokeColor(value, _readOnly_);}'} },
         ];
@@ -674,6 +675,7 @@ export class CurrentUserUtils {
                     CollectionViewType.Grid, CollectionViewType.NoteTaking]),
               title: "Perspective",                    toolTip: "View",                 btnType: ButtonType.DropdownList,                                                                                    ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}},
             { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton,         funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20,  scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
+            { title: "Num",     icon: "",           toolTip: "Frame Number",         btnType: ButtonType.TextButton,          funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement().currentFrame.toString()'}, width: 20,  scripts: {}},
             { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton,         funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20,  scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
             { title: "Fill",    icon: "fill-drip",     toolTip: "Background Fill Color",btnType: ButtonType.ColorButton,         funcs: {hidden: '!SelectionManager_selectedDocType()'},                     ignoreClick: true, width: 20,  scripts: { script:  'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected
             { title: "Header",  icon: "heading",       toolTip: "Header Color",         btnType: ButtonType.ColorButton,         funcs: {hidden: '!SelectionManager_selectedDocType()'},                     ignoreClick: true,             scripts: { script:  'return setHeaderColor(value, _readOnly_)'}},
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index ccd94c56e..d781a87ab 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -377,14 +377,14 @@ export namespace DragManager {
             }
             const rect = ele.getBoundingClientRect();
             const scaleX = rect.width / (ele.offsetWidth || rect.width);
-            const scaleY = ele.offsetHeight ? rect.height / (ele.offsetHeight || rect.height) : scaleX;
+            const scaleY = scaleX; //ele.offsetHeight ? rect.height / (ele.offsetHeight || rect.height) : scaleX;
 
             elesCont.left = Math.min(rect.left, elesCont.left);
             elesCont.top = Math.min(rect.top, elesCont.top);
             elesCont.right = Math.max(rect.right, elesCont.right);
             elesCont.bottom = Math.max(rect.bottom, elesCont.bottom);
-            xs.push(rect.left);
-            ys.push(rect.top);
+            xs.push(rect.left + (options?.offsetX || 0));
+            ys.push(rect.top + (options?.offsetY || 0));
             scaleXs.push(scaleX);
             scaleYs.push(scaleY);
             Object.assign(dragElement.style, {
@@ -401,9 +401,9 @@ export namespace DragManager {
                 transformOrigin: '0 0',
                 width: `${rect.width / scaleX}px`,
                 height: `${rect.height / scaleY}px`,
-                transform: `translate(${rect.left + (options?.offsetX || 0)}px, ${rect.top + (options?.offsetY || 0)}px) scale(${scaleX}, ${scaleY})`,
+                transform: `translate(${xs[0]}px, ${ys[0]}px) scale(${scaleX}, ${scaleY})`,
             });
-            dragLabel.style.transform = `translate(${rect.left + (options?.offsetX || 0)}px, ${rect.top + (options?.offsetY || 0) - 20}px)`;
+            dragLabel.style.transform = `translate(${xs[0]}px, ${ys[0] - 20}px)`;
 
             if (docsToDrag.length) {
                 const pdfBox = dragElement.getElementsByTagName('canvas');
@@ -543,8 +543,8 @@ export namespace DragManager {
             const moveVec = { x: x - lastPt.x, y: y - lastPt.y };
             lastPt = { x, y };
 
-            dragLabel.style.transform = `translate(${xs[0] + moveVec.x + (options?.offsetX || 0)}px, ${ys[0] + moveVec.y + (options?.offsetY || 0) - 20}px)`;
-            dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveVec.x) + (options?.offsetX || 0)}px, ${(ys[i] += moveVec.y) + (options?.offsetY || 0)}px)  scale(${scaleXs[i]}, ${scaleYs[i]})`));
+            dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveVec.x)}px, ${(ys[i] += moveVec.y)}px)  scale(${scaleXs[i]}, ${scaleYs[i]})`));
+            dragLabel.style.transform = `translate(${xs[0]}px, ${ys[0] - 20}px)`;
         };
         const upHandler = (e: PointerEvent) => {
             clearTimeout(startWindowDragTimer);
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index e5de7a0c5..2671aea56 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -23,9 +23,9 @@
 import React = require('react');
 import { action, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, WidthSym } from '../../fields/Doc';
+import { Doc, HeightSym, WidthSym } from '../../fields/Doc';
 import { InkData, InkField, InkTool } from '../../fields/InkField';
-import { Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
 import { OmitKeys, returnFalse, setupMoveUpEvents } from '../../Utils';
 import { CognitiveServices } from '../cognitive_services/CognitiveServices';
@@ -42,12 +42,13 @@ import { InkTangentHandles } from './InkTangentHandles';
 import { DocComponentView } from './nodes/DocumentView';
 import { FieldView, FieldViewProps } from './nodes/FieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { INK_MASK_SIZE } from './global/globalCssVariables.scss';
 import './InkStroke.scss';
 import Color = require('color');
 
 @observer
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
-    static readonly MaskDim = 50000; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big)
+    static readonly MaskDim = INK_MASK_SIZE; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big)
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(InkingStroke, fieldStr);
     }
@@ -353,9 +354,18 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const endMarker = StrCast(this.layoutDoc.strokeEndMarker);
         const markerScale = NumCast(this.layoutDoc.strokeMarkerScale, 1);
         const closed = InkingStroke.IsClosed(inkData);
-        const fillColor = StrCast(this.layoutDoc.fillColor, 'transparent');
+        const isInkMask = BoolCast(this.layoutDoc.isInkMask);
+        const fillColor = isInkMask ? '#aaaaaa' : StrCast(this.layoutDoc.fillColor, 'transparent');
         const strokeColor = !closed && fillColor && fillColor !== 'transparent' ? fillColor : StrCast(this.layoutDoc.color);
 
+        // bcz: Hack!! Not really sure why, but having fractional values for width/height of mask ink strokes causes the dragging clone (see DragManager) to be offset from where it should be.
+        if (isInkMask && (this.layoutDoc[WidthSym]() !== Math.round(this.layoutDoc[WidthSym]()) || this.layoutDoc[HeightSym]() !== Math.round(this.layoutDoc[HeightSym]()))) {
+            setTimeout(() => {
+                this.layoutDoc._width = Math.round(NumCast(this.layoutDoc[WidthSym]()));
+                this.layoutDoc._height = Math.round(NumCast(this.layoutDoc[HeightSym]()));
+            });
+        }
+
         // Visually renders the polygonal line made by the user.
         const inkLine = InteractionUtils.CreatePolyline(
             inkData,
@@ -430,7 +440,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                     className="inkStroke"
                     style={{
                         transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
-                        mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
+                        // mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
                         cursor: this.props.isSelected() ? 'default' : undefined,
                     }}
                     {...(!closed ? interactions : {})}>
@@ -467,7 +477,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                         className="inkStroke"
                         style={{
                             transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
-                            mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
+                            mixBlendMode: 'unset',
                             cursor: this.props.isSelected() ? 'default' : undefined,
                             position: 'absolute',
                         }}
@@ -489,6 +499,9 @@ export function SetActiveBezierApprox(bezier: string): void {
 export function SetActiveInkColor(value: string) {
     ActiveInkPen() && (ActiveInkPen().activeInkColor = value);
 }
+export function SetActiveIsInkMask(value: boolean) {
+    ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value);
+}
 export function SetActiveFillColor(value: string) {
     ActiveInkPen() && (ActiveInkPen().activeFillColor = value);
 }
@@ -513,6 +526,9 @@ export function ActiveInkColor(): string {
 export function ActiveFillColor(): string {
     return StrCast(ActiveInkPen()?.activeFillColor, '');
 }
+export function ActiveIsInkMask(): boolean {
+    return BoolCast(ActiveInkPen()?.activeIsInkMask, false);
+}
 export function ActiveArrowStart(): string {
     return StrCast(ActiveInkPen()?.activeArrowStart, '');
 }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index e96f65548..c166594e5 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -457,6 +457,7 @@ export class MainView extends React.Component {
                 fa.faVolumeDown,
                 fa.faSquareRootAlt,
                 fa.faVolumeMute,
+                fa.faUserCircle,
             ]
         );
         this.initAuthenticationRouters();
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 8432619de..eb55650e4 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1025,7 +1025,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
                                         <FontAwesomeIcon icon={'caret-left'} size={'lg'} />
                                     </div>
                                 </Tooltip>
-                                <Tooltip key="num" title={<div className="dash-tooltip">Toggle View All</div>} placement="bottom">
+                                <Tooltip key="num" title={<div className="dash-tooltip">Frame number</div>} placement="bottom">
                                     <div
                                         className="numKeyframe"
                                         style={{ color: this.props.docView.ComponentView?.getKeyFrameEditing?.() ? 'white' : 'black', backgroundColor: this.props.docView.ComponentView?.getKeyFrameEditing?.() ? '#5B9FDD' : '#AEDDF8' }}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index f30faab79..ff4b1e2ce 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -292,11 +292,11 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     pinDoc.presActiveFrame = pinProps?.activeFrame;
                     pinDoc.title = doc.title + ' (move)';
                     pinDoc.presMovement = PresMovement.Pan;
-                    if (pinDoc.isInkMask) {
-                        pinDoc.presHideAfter = true;
-                        pinDoc.presHideBefore = true;
-                        pinDoc.presMovement = PresMovement.None;
-                    }
+                }
+                if (pinDoc.isInkMask) {
+                    pinDoc.presHideAfter = true;
+                    pinDoc.presHideBefore = true;
+                    pinDoc.presMovement = PresMovement.None;
                 }
                 if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
                 PresBox.Instance?._selectedArray.clear();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 3d85d32a0..ee01c341b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -45,6 +45,7 @@ export interface PoolData {
 export interface ViewDefResult {
     ele: JSX.Element;
     bounds?: ViewDefBounds;
+    inkMask?: boolean;
 }
 function toLabel(target: FieldResult<Field>) {
     if (typeof target === 'number' || Number(target)) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 79e063f7f..010132aa5 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -1,4 +1,4 @@
-@import "../../global/globalCssVariables";
+@import '../../global/globalCssVariables';
 
 .collectionfreeformview-none {
     position: inherit;
@@ -20,8 +20,19 @@
     pointer-events: none;
 }
 
+.collectionfreeformview-mask {
+    mix-blend-mode: multiply;
+    z-index: 5000;
+    width: $INK_MASK_SIZE;
+    height: $INK_MASK_SIZE;
+    transform: translate($INK_MASK_SIZE_HALF, $INK_MASK_SIZE_HALF);
+    background-color: rgba(0, 0, 0, 0.7);
+    pointer-events: none;
+    position: absolute;
+}
+
 .collectionfreeformview-viewdef {
-    >.collectionFreeFormDocumentView-container {
+    > .collectionFreeFormDocumentView-container {
         pointer-events: none;
 
         .contentFittingDocumentDocumentView-previewDoc {
@@ -210,13 +221,13 @@
         }
     }
 
-    .collectionfreeformview>.jsx-parser {
+    .collectionfreeformview > .jsx-parser {
         position: inherit;
         height: 100%;
         width: 100%;
     }
 
-    >.jsx-parser {
+    > .jsx-parser {
         z-index: 0;
     }
 
@@ -268,6 +279,6 @@
 
 .pullpane-indicator {
     z-index: 99999;
-    background-color: rgba($color: #000000, $alpha: .4);
+    background-color: rgba($color: #000000, $alpha: 0.4);
     position: absolute;
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 82b377dfa..f3074543b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -34,7 +34,7 @@ import { COLLECTION_BORDER_WIDTH } from '../../../views/global/globalCssVariable
 import { Timeline } from '../../animationtimeline/Timeline';
 import { ContextMenu } from '../../ContextMenu';
 import { GestureOverlay } from '../../GestureOverlay';
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
 import { LightboxView } from '../../LightboxView';
 import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
 import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from '../../nodes/DocumentView';
@@ -121,7 +121,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @observable ChildDrag: DocumentView | undefined; // child document view being dragged.  needed to update drop areas of groups when a group item is dragged.
 
     @computed get views() {
-        return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele);
+        const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask).map(ele => ele.ele);
+        const renderableEles = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && !ele.inkMask).map(ele => ele.ele);
+        if (viewsMask.length) renderableEles.push(<div className="collectionfreeformview-mask">{viewsMask}</div>);
+        return renderableEles;
     }
     @computed get fitToContentVals() {
         return {
@@ -579,6 +582,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     ActiveArrowEnd(),
                     ActiveDash(),
                     points,
+                    ActiveIsInkMask(),
                     {
                         title: 'ink stroke',
                         x: B.x - ActiveInkWidth() / 2,
@@ -1519,6 +1523,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 elements.push({
                     ele: this.getChildDocView(entry[1]),
                     bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica),
+                    inkMask: BoolCast(entry[1].pair.layout.isInkMask),
                 })
             );
 
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss
index a14634bdc..c5968d9a7 100644
--- a/src/client/views/global/globalCssVariables.scss
+++ b/src/client/views/global/globalCssVariables.scss
@@ -1,4 +1,4 @@
-@import url("https://fonts.googleapis.com/css2?family=Roboto&display=swap");
+@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
 // colors
 $white: #ffffff;
 $off-white: #fdfdfd;
@@ -16,8 +16,10 @@ $yellow: #f5d747;
 
 $close-red: #e48282;
 
-$drop-shadow: "#32323215";
+$drop-shadow: '#32323215';
 
+$INK_MASK_SIZE: 50000;
+$INK_MASK_SIZE_HALF: -25000px;
 
 //padding
 $minimum-padding: 4px;
@@ -28,8 +30,7 @@ $large-padding: 32px;
 $icon-size: 28px;
 
 // fonts
-$sans-serif: "Roboto",
-sans-serif;
+$sans-serif: 'Roboto', sans-serif;
 $large-header: 16px;
 $body-text: 12px;
 $small-text: 9px;
@@ -54,7 +55,6 @@ $standard-border-radius: 3px;
 // shadow
 $standard-box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
 
-
 $dashboardselector-height: 32px;
 $mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc
 $docDecorations-zindex: 998; // then doc decorations appear over everything else
@@ -79,4 +79,5 @@ $TREE_BULLET_WIDTH: 20px;
     DFLT_IMAGE_NATIVE_DIM: $DFLT_IMAGE_NATIVE_DIM;
     LEFT_MENU_WIDTH: $LEFT_MENU_WIDTH;
     TREE_BULLET_WIDTH: $TREE_BULLET_WIDTH;
-}
\ No newline at end of file
+    INK_MASK_SIZE: $INK_MASK_SIZE;
+}
diff --git a/src/client/views/global/globalCssVariables.scss.d.ts b/src/client/views/global/globalCssVariables.scss.d.ts
index 59c2b3585..76259113c 100644
--- a/src/client/views/global/globalCssVariables.scss.d.ts
+++ b/src/client/views/global/globalCssVariables.scss.d.ts
@@ -1,6 +1,5 @@
-
 interface IGlobalScss {
-    contextMenuZindex: string;  // context menu shows up over everything
+    contextMenuZindex: string; // context menu shows up over everything
     SCHEMA_DIVIDER_WIDTH: string;
     COLLECTION_BORDER_WIDTH: string;
     MINIMIZED_ICON_SIZE: string;
@@ -11,7 +10,8 @@ interface IGlobalScss {
     DFLT_IMAGE_NATIVE_DIM: string;
     LEFT_MENU_WIDTH: string;
     TREE_BULLET_WIDTH: string;
+    INK_MASK_SIZE: number;
 }
 declare const globalCssVariables: IGlobalScss;
 
-export = globalCssVariables;
\ No newline at end of file
+export = globalCssVariables;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index e154e8445..78f351f4f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -38,11 +38,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     get displayName() {
         return 'CollectionFreeFormDocumentView(' + this.rootDoc.title + ')';
     } // this makes mobx trace() statements more descriptive
-    get maskCentering() {
-        return this.props.Document.isInkMask ? InkingStroke.MaskDim / 2 : 0;
-    }
     get transform() {
-        return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`;
+        return `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`;
     }
     get X() {
         return this.dataProvider ? this.dataProvider.x : NumCast(this.Document.x);
@@ -194,7 +191,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             PanelHeight: this.panelHeight,
         };
         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);
+        const mixBlendMode = undefined; // (StrCast(this.layoutDoc.mixBlendMode) as any) || (typeof background === 'string' && background && !background.startsWith('linear') && DashColor(background).alpha() !== 1 ? 'multiply' : undefined);
         return (
             <div
                 className={'collectionFreeFormDocumentView-container'}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f9ef85595..bcc55eab4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1640,8 +1640,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     render() {
         TraceMobx();
-        const xshift = () => (this.props.Document.isInkMask && !this.props.LayoutTemplateString && !this.props.LayoutTemplate?.() ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
-        const yshift = () => (this.props.Document.isInkMask && !this.props.LayoutTemplateString && !this.props.LayoutTemplate?.() ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
+        const xshift = () => (Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
+        const yshift = () => (Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
         const isPresTreeElement: boolean = this.props.treeViewDoc?.type === DocumentType.PRES;
         const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
         return (
@@ -1652,7 +1652,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                         ref={this.ContentRef}
                         style={{
                             transition: this.props.dataTransition,
-                            position: this.props.Document.isInkMask ? 'absolute' : undefined,
+                            //position: this.props.Document.isInkMask ? 'absolute' : undefined,
                             transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
                             width: isButton || isPresTreeElement ? '100%' : xshift() ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`,
                             height:
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index d3b95e25a..cb68c1ac3 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -1,6 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
+import Color from 'color';
 import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -21,7 +22,7 @@ import { DocComponent } from '../../DocComponent';
 import { EditableView } from '../../EditableView';
 import { GestureOverlay } from '../../GestureOverlay';
 import { Colors } from '../../global/globalEnums';
-import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
+import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, SetActiveIsInkMask } from '../../InkingStroke';
 import { InkTranscription } from '../../InkTranscription';
 import { StyleProp } from '../../StyleProvider';
 import { FieldView, FieldViewProps } from '.././FieldView';
@@ -498,8 +499,6 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 </div>
             );
 
-        const buttonText = StrCast(this.rootDoc.buttonText);
-
         // TODO:glr Add label of button type
         let button: JSX.Element | null = this.defaultButton;
 
@@ -508,7 +507,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 button = (
                     <div className={`menuButton ${this.type}`} style={{ color, backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}>
                         <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
-                        {buttonText ? <div className="button-text">{buttonText}</div> : null}
+                        {StrCast(this.rootDoc.buttonText) ? <div className="button-text">{StrCast(this.rootDoc.buttonText)}</div> : null}
                         {label}
                     </div>
                 );
@@ -563,11 +562,13 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 break;
         }
 
-        return !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? (
-            button
-        ) : button !== null ? (
-            <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>{button}</Tooltip>
-        ) : null;
+        const retval =
+            !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? (
+                button
+            ) : button !== null ? (
+                <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>{button}</Tooltip>
+            ) : null;
+        return retval;
     }
 }
 
@@ -860,6 +861,21 @@ ScriptingGlobals.add(function setActiveTool(tool: string, checkResult?: boolean)
     }
 });
 
+// toggle: Set overlay status of selected document
+ScriptingGlobals.add(function setIsInkMask(checkResult?: boolean) {
+    const selected = SelectionManager.Docs().lastElement();
+    if (checkResult) {
+        if (selected?.type === DocumentType.INK) {
+            return BoolCast(selected.isInkMask) ? Colors.MEDIUM_BLUE : 'transparent';
+        }
+        return ActiveIsInkMask() ? Colors.MEDIUM_BLUE : 'transparent';
+    }
+    SetActiveIsInkMask(!ActiveIsInkMask());
+    SelectionManager.Docs()
+        .filter(doc => doc.type === DocumentType.INK)
+        .map(doc => (doc.isInkMask = !doc.isInkMask));
+});
+
 // toggle: Set overlay status of selected document
 ScriptingGlobals.add(function setFillColor(color?: string, checkResult?: boolean) {
     const selected = SelectionManager.Docs().lastElement();
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 3bbdce1e4..627f35e71 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -313,10 +313,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     //it'll also execute the necessary actions if presentation is playing.
     public gotoDocument = action((index: number, from?: Doc, group?: boolean) => {
         Doc.UnBrushAllDocs();
+
         if (index >= 0 && index < this.childDocs.length) {
             this.rootDoc._itemIndex = index;
             const activeItem: Doc = this.activeItem;
             const targetDoc: Doc = this.targetDoc;
+            if (activeItem.presActiveFrame !== undefined) {
+                const context = DocCast(DocCast(activeItem.presentationTargetDoc).context);
+                context && CollectionFreeFormViewChrome.gotoKeyFrame(context, NumCast(activeItem.presActiveFrame));
+            }
             if (from?.mediaStopTriggerList && this.layoutDoc.presStatus !== PresStatus.Edit) {
                 DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia);
             }
@@ -516,20 +521,26 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             if (tagDoc === this.layoutDoc.presCollection) {
                 tagDoc.opacity = 1;
             } else {
-                if (itemIndexes.length > 1 && curDoc.presHideBefore && curInd !== 0) {
-                } else if (curDoc.presHideBefore) {
-                    if (index > this.itemIndex) {
-                        tagDoc.opacity = 0;
-                    } else if (index === this.itemIndex || !curDoc.presHideAfter) {
+                if (curDoc.presHideBefore) {
+                    if (itemIndexes.length > 1 && curInd !== 0) {
                         tagDoc.opacity = 1;
+                    } else {
+                        if (index > this.itemIndex) {
+                            tagDoc.opacity = 0;
+                        } else if (index === this.itemIndex || !curDoc.presHideAfter) {
+                            tagDoc.opacity = 1;
+                        }
                     }
                 }
-                if (itemIndexes.length > 1 && curDoc.presHideAfter && curInd !== itemIndexes.length - 1) {
-                } else if (curDoc.presHideAfter) {
-                    if (index < this.itemIndex) {
-                        tagDoc.opacity = 0;
-                    } else if (index === this.itemIndex || !curDoc.presHideBefore) {
+                if (curDoc.presHideAfter) {
+                    if (itemIndexes.length > 1 && curInd !== itemIndexes.length - 1) {
                         tagDoc.opacity = 1;
+                    } else {
+                        if (index < this.itemIndex) {
+                            tagDoc.opacity = 0;
+                        } else if (index === this.itemIndex || !curDoc.presHideBefore) {
+                            tagDoc.opacity = 1;
+                        }
                     }
                 }
             }
@@ -821,11 +832,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem);
         if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(context), 0);
         else this.updateCurrentPresentation(context);
-
-        if (this.activeItem.presActiveFrame !== undefined) {
-            const context = DocCast(DocCast(this.activeItem.presentationTargetDoc).context);
-            context && CollectionFreeFormViewChrome.gotoKeyFrame(context, NumCast(this.activeItem.presActiveFrame));
-        }
     };
 
     //Command click
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index 114d5fc2f..a074098c1 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -1,22 +1,21 @@
-import { Bezier } from "bezier-js";
-import { createSimpleSchema, list, object, serializable } from "serializr";
-import { ScriptingGlobals } from "../client/util/ScriptingGlobals";
-import { Deserializable } from "../client/util/SerializationHelper";
-import { Copy, ToScriptString, ToString } from "./FieldSymbols";
-import { ObjectField } from "./ObjectField";
+import { Bezier } from 'bezier-js';
+import { createSimpleSchema, list, object, serializable } from 'serializr';
+import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
+import { Deserializable } from '../client/util/SerializationHelper';
+import { Copy, ToScriptString, ToString } from './FieldSymbols';
+import { ObjectField } from './ObjectField';
 
 // Helps keep track of the current ink tool in use.
 export enum InkTool {
-    None = "none",
-    Pen = "pen",
-    Highlighter = "highlighter",
-    Eraser = "eraser",
-    Stamp = "stamp",
-    Write = "write",
-    PresentationPin = 'presentationpin'
+    None = 'none',
+    Pen = 'pen',
+    Highlighter = 'highlighter',
+    Eraser = 'eraser',
+    Stamp = 'stamp',
+    Write = 'write',
+    PresentationPin = 'presentationpin',
 }
 
-
 // Defines a point in an ink as a pair of x- and y-coordinates.
 export interface PointData {
     X: number;
@@ -54,15 +53,16 @@ export interface HandleLine {
 }
 
 const pointSchema = createSimpleSchema({
-    X: true, Y: true
+    X: true,
+    Y: true,
 });
 
 const strokeDataSchema = createSimpleSchema({
     pathData: list(object(pointSchema)),
-    "*": true
+    '*': true,
 });
 
-@Deserializable("ink")
+@Deserializable('ink')
 export class InkField extends ObjectField {
     @serializable(list(object(strokeDataSchema)))
     readonly inkData: InkData;
@@ -85,11 +85,11 @@ export class InkField extends ObjectField {
     }
 
     [ToScriptString]() {
-        return "new InkField([" + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}`) + "])";
+        return 'new InkField([' + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}}`) + '])';
     }
     [ToString]() {
-        return "InkField";
+        return 'InkField';
     }
 }
 
-ScriptingGlobals.add("InkField", InkField);
\ No newline at end of file
+ScriptingGlobals.add('InkField', InkField);
-- 
cgit v1.2.3-70-g09d2


From 0178de4ab9ffd11630b700f9c02468b74beabd14 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 17 Aug 2022 12:31:26 -0400
Subject: fixed dragging docs on web and pdf to work better by temporarily
 adding transparent docs to the opaque layer so they can get drop events.  
 cleaned eraser and pen interaction code and made erasing strokes work faster
 and avoid hanging by not intersecting strokes that are already partially
 deleted.

---
 src/Utils.ts                                       |   3 +-
 src/client/documents/Documents.ts                  |  26 +---
 src/client/views/collections/CollectionSubView.tsx |  12 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 140 ++++++---------------
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   5 +-
 src/client/views/nodes/DocumentView.tsx            |   6 +-
 src/client/views/nodes/VideoBox.tsx                |  11 +-
 src/client/views/nodes/WebBox.scss                 |  17 +--
 src/client/views/nodes/WebBox.tsx                  | 110 ++++++++--------
 src/client/views/pdf/PDFViewer.tsx                 |  75 +++++------
 10 files changed, 153 insertions(+), 252 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 528a429d0..9e002ebd4 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -111,6 +111,7 @@ export namespace Utils {
 
     const isTransparentFunctionHack = 'isTransparent(__value__)';
     export const noRecursionHack = '__noRecursion';
+    export const noDragsDocFilter = 'noDragDocs:any:check';
     export function IsRecursiveFilter(val: string) {
         return !val.includes(noRecursionHack);
     }
@@ -125,7 +126,7 @@ export namespace Utils {
         // bcz: isTransparent(__value__) is a hack.  it would be nice to have acual functions be parsed, but now Doc.matchFieldValue is hardwired to recognize just this one
         return `backgroundColor:${isTransparentFunctionHack},${noRecursionHack}:x`; // bcz: hack.  noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field
     }
-    export function PropUnsetFilter(prop: string) {
+    export function IsPropUnsetFilter(prop: string) {
         return `${prop}:any,${noRecursionHack}:unset`;
     }
 
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index cf9ed43e1..e579bfd8a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1143,30 +1143,6 @@ export namespace Docs {
 }
 
 export namespace DocUtils {
-    export function Excluded(d: Doc, docFilters: string[]) {
-        const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields
-        docFilters.forEach(filter => {
-            const fields = filter.split(':');
-            const key = fields[0];
-            const value = fields[1];
-            const modifiers = fields[2];
-            if (!filterFacets[key]) {
-                filterFacets[key] = {};
-            }
-            filterFacets[key][value] = modifiers;
-        });
-
-        if (d.z) return false;
-        for (const facetKey of Object.keys(filterFacets)) {
-            const facet = filterFacets[facetKey];
-            const xs = Object.keys(facet).filter(value => facet[value] === 'x');
-            const failsNotEqualFacets = xs?.some(value => Doc.matchFieldValue(d, facetKey, value));
-            if (failsNotEqualFacets) {
-                return true;
-            }
-        }
-        return false;
-    }
     /**
      * @param docs
      * @param docFilters
@@ -1200,7 +1176,7 @@ export namespace DocUtils {
                       return false;
                   }
 
-                  for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies')) {
+                  for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== Utils.noDragsDocFilter.split(':')[0])) {
                       const facet = filterFacets[facetKey];
 
                       // facets that match some value in the field of the document (e.g. some text field)
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 5479929bd..e33bb77de 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -88,11 +88,11 @@ export function CollectionSubView<X>(moreProps?: X) {
         }
         collectionFilters = () => this._focusFilters ?? StrListCast(this.props.Document._docFilters);
         collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.props.Document._docRangeFilters, listSpec('string'), []);
+        // child filters apply to the descendants of the documents in this collection
         childDocFilters = () => [...(this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()];
+        // unrecursive filters apply to the documents in the collection, but no their children.  See Utils.noRecursionHack
         unrecursiveDocFilters = () => [...(this.props.docFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])];
         childDocRangeFilters = () => [...(this.props.docRangeFilters?.() || []), ...this.collectionRangeDocFilters()];
-        IsFiltered = () =>
-            this.collectionFilters().length || this.collectionRangeDocFilters().length ? 'hasFilter' : this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? 'inheritsFilter' : undefined;
         searchFilterDocs = () => this.props.searchFilterDocs?.() ?? DocListCast(this.props.Document._searchFilterDocs);
         @computed.struct get childDocs() {
             TraceMobx();
@@ -122,13 +122,11 @@ export function CollectionSubView<X>(moreProps?: X) {
                 return childDocs.filter(cd => !cd.cookies); // remove any documents that require a cookie if there are no filters to provide one
             }
 
-            // console.log(Doc.ActiveDashboard._docFilters);
-            // if (!this.props.Document._docFilters && this.props.Document.currentFilter) {
-            //     (this.props.Document.currentFilter as Doc).filterBoolean = (this.props.ContainingCollectionDoc?.currentFilter as Doc)?.filterBoolean;
-            // }
             const docsforFilter: Doc[] = [];
             childDocs.forEach(d => {
-                // if (DocUtils.Excluded(d, docFilters)) return;
+                // dragging facets
+                const dragged = this.props.docFilters?.().some(f => f.includes(Utils.noDragsDocFilter));
+                if (dragged && DragManager.docsBeingDragged.includes(d)) return false;
                 let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), docRangeFilters, viewSpecScript, this.props.Document).length > 0;
                 if (notFiltered) {
                     notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, docRangeFilters, viewSpecScript, this.props.Document).length > 0;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f3074543b..45a5e30ff 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -109,8 +109,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @observable _hLines: number[] | undefined;
     @observable _vLines: number[] | undefined;
     @observable _firstRender = true; // this turns off rendering of the collection's content so that there's instant feedback when a tab is switched of what content will be shown.
-    @observable _pullCoords: number[] = [0, 0];
-    @observable _pullDirection: string = '';
     @observable _showAnimTimeline = false;
     @observable _clusterSets: Doc[][] = [];
     @observable _deleteList: DocumentView[] = [];
@@ -465,20 +463,23 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     getClusterColor = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => {
         let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props'  used to be renderDepth + 1
-        if (property !== StyleProp.BackgroundColor) return styleProp;
-        const cluster = NumCast(doc?.cluster);
-        if (this.Document._useClusters) {
-            if (this._clusterSets.length <= cluster) {
-                setTimeout(() => doc && this.updateCluster(doc));
-            } else {
-                // choose a cluster color from a palette
-                const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)'];
-                styleProp = colors[cluster % colors.length];
-                const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
-                // override the cluster color with an explicitly set color on a non-background document.  then override that with an explicitly set color on a background document
-                set?.map(s => (styleProp = StrCast(s.backgroundColor)));
-            }
-        } //else if (doc && NumCast(doc.group, -1) !== -1) styleProp = "gray";
+        switch (property) {
+            case StyleProp.BackgroundColor:
+                const cluster = NumCast(doc?.cluster);
+                if (this.Document._useClusters) {
+                    if (this._clusterSets.length <= cluster) {
+                        setTimeout(() => doc && this.updateCluster(doc));
+                    } else {
+                        // choose a cluster color from a palette
+                        const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)'];
+                        styleProp = colors[cluster % colors.length];
+                        const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
+                        // override the cluster color with an explicitly set color on a non-background document.  then override that with an explicitly set color on a background document
+                        set?.map(s => (styleProp = StrCast(s.backgroundColor)));
+                    }
+                }
+                break;
+        }
         return styleProp;
     };
 
@@ -523,17 +524,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     case InkTool.Pen:
                         break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views
                     case InkTool.Eraser:
-                        document.addEventListener('pointermove', this.onEraserMove);
-                        document.addEventListener('pointerup', this.onEraserUp);
                         this._batch = UndoManager.StartBatch('collectionErase');
-                        e.stopPropagation();
-                        e.preventDefault();
+                        setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, emptyFunction);
                         break;
                     case InkTool.None:
                         if (!(this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
                             this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
-                            document.addEventListener('pointermove', this.onPointerMove);
-                            document.addEventListener('pointerup', this.onPointerUp);
+                            setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, false);
                         }
                         break;
                 }
@@ -698,23 +695,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
     @action
     onEraserUp = (e: PointerEvent): void => {
-        if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
-            document.removeEventListener('pointermove', this.onEraserMove);
-            document.removeEventListener('pointerup', this.onEraserUp);
-            this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.rootDoc));
-            this._deleteList = [];
-            this._batch?.end();
-        }
-    };
-
-    @action
-    onPointerUp = (e: PointerEvent): void => {
-        if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
-            document.removeEventListener('pointermove', this.onPointerMove);
-            document.removeEventListener('pointerup', this.onPointerUp);
-            this.removeMoveListeners();
-            this.removeEndListeners();
-        }
+        this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.rootDoc));
+        this._deleteList = [];
+        this._batch?.end();
     };
 
     onClick = (e: React.MouseEvent) => {
@@ -752,46 +735,42 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
      *  However, if Shift is held, then no segmentation is done -- instead any intersected stroke is deleted in its entirety.
      */
     @action
-    onEraserMove = (e: PointerEvent) => {
+    onEraserMove = (e: PointerEvent, down: number[], delta: number[]) => {
         const currPoint = { X: e.clientX, Y: e.clientY };
-        this.getEraserIntersections({ X: this._lastX, Y: this._lastY }, currPoint).forEach(intersect => {
+        this.getEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint).forEach(intersect => {
             if (!this._deleteList.includes(intersect.inkView)) {
                 this._deleteList.push(intersect.inkView);
                 SetActiveInkWidth(StrCast(intersect.inkView.rootDoc.strokeWidth?.toString()) || '1');
                 SetActiveInkColor(StrCast(intersect.inkView.rootDoc.color?.toString()) || 'black');
                 // create a new curve by appending all curves of the current segment together in order to render a single new stroke.
-                !e.shiftKey &&
+                if (!e.shiftKey) {
                     this.segmentInkStroke(intersect.inkView, intersect.t).forEach(segment =>
                         GestureOverlay.Instance.dispatchGesture(
                             GestureUtils.Gestures.Stroke,
                             segment.reduce((data, curve) => [...data, ...curve.points.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[])
                         )
                     );
+                }
                 // Lower ink opacity to give the user a visual indicator of deletion.
                 intersect.inkView.layoutDoc.opacity = 0.5;
+                intersect.inkView.layoutDoc.dontIntersect = true;
             }
         });
-        this._lastX = currPoint.X;
-        this._lastY = currPoint.Y;
-
-        e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
-        e.preventDefault();
+        return false;
     };
 
     @action
-    onPointerMove = (e: PointerEvent): void => {
-        if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return;
+    onPointerMove = (e: PointerEvent): boolean => {
+        if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return false;
         if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
             Doc.ActiveTool = InkTool.None;
             if (this.props.isContentActive(true)) e.stopPropagation();
         } else if (!e.cancelBubble) {
             if (this.tryDragCluster(e, this._hitCluster)) {
-                document.removeEventListener('pointermove', this.onPointerMove);
-                document.removeEventListener('pointerup', this.onPointerUp);
+                return true;
             } else this.pan(e);
-            e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
-            e.preventDefault();
         }
+        return false;
     };
 
     /**
@@ -801,6 +780,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     getEraserIntersections = (lastPoint: { X: number; Y: number }, currPoint: { X: number; Y: number }) => {
         const eraserMin = { X: Math.min(lastPoint.X, currPoint.X), Y: Math.min(lastPoint.Y, currPoint.Y) };
         const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) };
+
         return this.childDocs
             .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView))
             .filter(inkView => inkView?.ComponentView instanceof InkingStroke)
@@ -888,7 +868,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const tVals: number[] = [];
         // Iterating through all ink strokes in the current freeform collection.
         this.childDocs
-            .filter(doc => doc.type === DocumentType.INK)
+            .filter(doc => doc.type === DocumentType.INK && !doc.dontIntersect)
             .forEach(doc => {
                 const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)?.ComponentView as InkingStroke;
                 const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
@@ -919,7 +899,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
                         e.preventDefault();
                         document.removeEventListener('pointermove', this.onPointerMove);
-                        document.removeEventListener('pointerup', this.onPointerUp);
                         return;
                     }
                     // TODO: nda - this allows us to pan collections with finger -> only want to do this when collection is selected'
@@ -967,13 +946,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
                         // const transformed = this.getTransform().inverse().transformPoint(centerX, centerY);
 
-                        if (!this._pullDirection) {
-                            // if we are not bezel movement
-                            this.pan({ clientX: centerX, clientY: centerY });
-                        } else {
-                            this._pullCoords = [centerX, centerY];
-                        }
-
                         this._lastX = centerX;
                         this._lastY = centerY;
                     }
@@ -998,24 +970,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
                 this._lastX = centerX;
                 this._lastY = centerY;
-                const screenBox = this._mainCont?.getBoundingClientRect();
-
-                // determine if we are using a bezel movement
-                if (screenBox) {
-                    if (screenBox.right - centerX < 100) {
-                        this._pullCoords = [centerX, centerY];
-                        this._pullDirection = 'right';
-                    } else if (centerX - screenBox.left < 100) {
-                        this._pullCoords = [centerX, centerY];
-                        this._pullDirection = 'left';
-                    } else if (screenBox.bottom - centerY < 100) {
-                        this._pullCoords = [centerX, centerY];
-                        this._pullDirection = 'bottom';
-                    } else if (centerY - screenBox.top < 100) {
-                        this._pullCoords = [centerX, centerY];
-                        this._pullDirection = 'top';
-                    }
-                }
 
                 this.removeMoveListeners();
                 this.addMoveListeners();
@@ -1027,19 +981,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     cleanUpInteractions = () => {
-        switch (this._pullDirection) {
-            case 'left':
-            case 'right':
-            case 'top':
-            case 'bottom':
-                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: 'New Collection' }), this._pullDirection);
-        }
-
-        this._pullDirection = '';
-        this._pullCoords = [0, 0];
-
-        document.removeEventListener('pointermove', this.onPointerMove);
-        document.removeEventListener('pointerup', this.onPointerUp);
         this.removeMoveListeners();
         this.removeEndListeners();
     };
@@ -1380,7 +1321,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const { z, color, zIndex } = params.pair.layout;
         const { x, y, opacity } =
             this.Document._currentFrame === undefined
-                ? { x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.Opacity) }
+                ? { x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.childOpacity ? this.props.childOpacity() : this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.Opacity) }
                 : CollectionFreeFormDocumentView.getValues(params.pair.layout, NumCast(this.Document._currentFrame));
         return {
             x: NumCast(x),
@@ -1915,7 +1856,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 getContainerTransform={this.getContainerTransform}
                 getTransform={this.getTransform}
                 isAnnotationOverlay={this.isAnnotationOverlay}>
-                <div className="marqueeView-div" ref={this._marqueeRef} style={{ opacity: this.props.dontRenderDocuments ? 0 : undefined }}>
+                <div className="marqueeView-div" ref={this._marqueeRef} style={{ opacity: this.props.dontRenderDocuments ? 0.7 : undefined }}>
                     {this.layoutDoc._backgroundGridShow ? (
                         <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!!?
@@ -1996,15 +1937,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 {this._firstRender ? this.placeholder : this.marqueeView}
                 {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
 
-                <div
-                    className={'pullpane-indicator'}
-                    style={{
-                        display: this._pullDirection ? 'block' : 'none',
-                        top: clientRect ? (this._pullDirection === 'bottom' ? this._pullCoords[1] - clientRect.y : 0) : 'auto',
-                        left: clientRect ? (this._pullDirection === 'right' ? this._pullCoords[0] - clientRect.x : 0) : 'auto',
-                        width: clientRect ? (this._pullDirection === 'left' ? this._pullCoords[0] - clientRect.left : this._pullDirection === 'right' ? clientRect.right - this._pullCoords[0] : clientRect.width) : 0,
-                        height: clientRect ? (this._pullDirection === 'top' ? this._pullCoords[1] - clientRect.top : this._pullDirection === 'bottom' ? clientRect.bottom - this._pullCoords[1] : clientRect.height) : 0,
-                    }}></div>
                 {
                     // uncomment to show snap lines
                     <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 78f351f4f..e19e2d525 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable, trace } from 'mobx';
+import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Opt } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
@@ -6,13 +6,12 @@ import { listSpec } from '../../../fields/Schema';
 import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { DashColor, numberRange, OmitKeys } from '../../../Utils';
+import { numberRange } from '../../../Utils';
 import { DocumentManager } from '../../util/DocumentManager';
 import { SelectionManager } from '../../util/SelectionManager';
 import { Transform } from '../../util/Transform';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { DocComponent } from '../DocComponent';
-import { InkingStroke } from '../InkingStroke';
 import { StyleProp } from '../StyleProvider';
 import './CollectionFreeFormDocumentView.scss';
 import { DocumentView, DocumentViewProps } from './DocumentView';
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index bcc55eab4..172adcafe 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -970,7 +970,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     collectionFilters = () => StrListCast(this.props.Document._docFilters);
     collectionRangeDocFilters = () => StrListCast(this.props.Document._docRangeFilters);
     @computed get showFilterIcon() {
-        return this.collectionFilters().length || this.collectionRangeDocFilters().length ? 'hasFilter' : this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? 'inheritsFilter' : undefined;
+        return this.collectionFilters().length || this.collectionRangeDocFilters().length
+            ? 'hasFilter'
+            : this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f) && f !== Utils.noDragsDocFilter).length || this.props.docRangeFilters().length
+            ? 'inheritsFilter'
+            : undefined;
     }
     rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
     panelHeight = () => this.props.PanelHeight() - this.headerMargin;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index a3d501153..0ff15f93b 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -831,16 +831,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     // stretches vertically or horizontally depending on video orientation so video fits full screen
     fullScreenSize() {
         if (this._videoRef && this._videoRef.videoHeight / this._videoRef.videoWidth > 1) {
-            return { height: '100%' };
-        } else {
-            return { width: '100%' };
+            //prettier-ignore
+            return ({ height: '100%' });
         }
+        //prettier-ignore
+        return ({ width: '100%' });
     }
 
     // for zoom slider, sets timeline waveform zoom
-    zoom = (zoom: number) => {
-        this.timeline?.setZoom(zoom);
-    };
+    zoom = (zoom: number) => this.timeline?.setZoom(zoom);
 
     // plays link
     playLink = (doc: Doc) => {
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index d8dd074a5..85986ff27 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -1,9 +1,11 @@
-@import "../global/globalCssVariables.scss";
-
+@import '../global/globalCssVariables.scss';
 
 .webBox {
     height: 100%;
-    position: relative;
+    width: 100%;
+    top: 0;
+    left: 0;
+    position: absolute;
     display: flex;
 
     .webBox-sideResizer {
@@ -84,7 +86,6 @@
             background: none;
         }
 
-
         .webBox-overlayCont {
             position: absolute;
             width: calc(100% - 40px);
@@ -95,7 +96,7 @@
             justify-content: center;
             align-items: center;
             overflow: hidden;
-            transition: left .5s;
+            transition: left 0.5s;
             pointer-events: all;
 
             .webBox-searchBar {
@@ -158,7 +159,7 @@
             left: 0;
             cursor: text;
             padding: 15px;
-            height: 100%
+            height: 100%;
         }
 
         .webBox-cont {
@@ -235,7 +236,7 @@
         height: 25px;
         align-items: center;
 
-        >svg {
+        > svg {
             margin: auto;
         }
     }
@@ -257,4 +258,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index ca9f363c1..6c2e42f86 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-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 WebRequest from 'web-request';
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
@@ -11,7 +11,7 @@ import { listSpec } from '../../../fields/Schema';
 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, Utils } from '../../../Utils';
+import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SnappingManager } from '../../util/SnappingManager';
@@ -35,6 +35,7 @@ import { LinkDocPreview } from './LinkDocPreview';
 import { VideoBox } from './VideoBox';
 import './WebBox.scss';
 import React = require('react');
+import { DragManager } from '../../util/DragManager';
 const { CreateImage } = require('./WebBoxRenderer');
 const _global = (window /* browser */ || global) /* node */ as any;
 const htmlToText = require('html-to-text');
@@ -818,6 +819,56 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return this._showSidebar || this.layoutDoc._showSidebar ? true : false;
     }
 
+    @computed get webpage() {
+        const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
+        const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any);
+        const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
+        const renderAnnotations = (docFilters?: () => string[]) => (
+            <CollectionFreeFormView
+                {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                renderDepth={this.props.renderDepth + 1}
+                isAnnotationOverlay={true}
+                fieldKey={this.annotationKey}
+                CollectionView={undefined}
+                setPreviewCursor={this.setPreviewCursor}
+                PanelWidth={this.panelWidth}
+                PanelHeight={this.panelHeight}
+                ScreenToLocalTransform={this.scrollXf}
+                NativeDimScaling={returnOne}
+                dropAction={'alias'}
+                docFilters={docFilters}
+                select={emptyFunction}
+                bringToFront={emptyFunction}
+                styleProvider={this.childStyleProvider}
+                whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+                removeDocument={this.removeDocument}
+                moveDocument={this.moveDocument}
+                addDocument={this.addDocument}
+                childPointerEvents={this.props.isContentActive() ? 'all' : undefined}
+                pointerEvents={this.annotationPointerEvents}
+            />
+        );
+        return (
+            <div
+                className={'webBox-outerContent'}
+                ref={this._outerRef}
+                style={{
+                    height: `${100 / scale}%`,
+                    pointerEvents,
+                }}
+                onWheel={StopEvent} // block wheel events from propagating since they're handled by the iframe
+                onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
+                onPointerDown={this.onMarqueeDown}>
+                <div className={'webBox-innerContent'} style={{ height: this._webPageHasBeenRendered ? NumCast(this.scrollHeight, 50) : '100%', pointerEvents }}>
+                    {this.content}
+                    {<div style={{ display: DragManager.docsBeingDragged.length ? 'none' : undefined, mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>}
+                    {renderAnnotations(this.opaqueFilter)}
+                    {this.annotationLayer}
+                </div>
+            </div>
+        );
+    }
+
     @computed get searchUI() {
         return (
             <div className="webBox-ui" onPointerDown={e => e.stopPropagation()} style={{ display: this.props.isContentActive() ? 'flex' : 'none' }}>
@@ -859,9 +910,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
     scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop));
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
-    basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter('textInlineAnnotations')];
     transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
-    opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()];
+    opaqueFilter = () => [...this.props.docFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])];
     childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
             if (doc.textInlineAnnotations) return 'none';
@@ -871,37 +921,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     pointerEvents = () => (!this._draggingSidebar && this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none');
     annotationPointerEvents = () => (this._isAnnotating || SnappingManager.GetIsDragging() ? 'all' : 'none');
     render() {
-        const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any);
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
+        const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any);
         const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
-        const renderAnnotations = (docFilters?: () => string[]) => (
-            <CollectionFreeFormView
-                {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
-                renderDepth={this.props.renderDepth + 1}
-                isAnnotationOverlay={true}
-                fieldKey={this.annotationKey}
-                CollectionView={undefined}
-                setPreviewCursor={this.setPreviewCursor}
-                PanelWidth={this.panelWidth}
-                PanelHeight={this.panelHeight}
-                ScreenToLocalTransform={this.scrollXf}
-                NativeDimScaling={returnOne}
-                dropAction={'alias'}
-                docFilters={docFilters || this.basicFilter}
-                dontRenderDocuments={docFilters ? false : true}
-                select={emptyFunction}
-                bringToFront={emptyFunction}
-                whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
-                removeDocument={this.removeDocument}
-                moveDocument={this.moveDocument}
-                addDocument={this.addDocument}
-                styleProvider={this.childStyleProvider}
-                childPointerEvents={this.props.isContentActive() ? 'all' : undefined}
-                pointerEvents={this.annotationPointerEvents}
-            />
-        );
         return (
-            <div className="webBox" ref={this._mainCont} style={{ pointerEvents: this.pointerEvents(), display: this.props.thumbShown?.() ? 'none' : undefined }}>
+            <div className="webBox" ref={this._mainCont} style={{ pointerEvents: this.pointerEvents(), display: !SnappingManager.GetIsDragging() && this.props.thumbShown?.() ? 'none' : undefined }}>
                 <div className="webBox-background" style={{ backgroundColor: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor) }} />
                 <div
                     className="webBox-container"
@@ -911,27 +935,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         pointerEvents,
                     }}
                     onContextMenu={this.specificContextMenu}>
-                    <div
-                        className={'webBox-outerContent'}
-                        ref={this._outerRef}
-                        style={{
-                            height: `${100 / scale}%`,
-                            pointerEvents,
-                        }}
-                        onWheel={e => {
-                            e.stopPropagation();
-                            e.preventDefault();
-                        }} // block wheel events from propagating since they're handled by the iframe
-                        onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
-                        onPointerDown={this.onMarqueeDown}>
-                        <div className={'webBox-innerContent'} style={{ height: this._webPageHasBeenRendered ? NumCast(this.scrollHeight, 50) : '100%', pointerEvents }}>
-                            {this.content}
-                            <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
-                            {renderAnnotations(this.opaqueFilter)}
-                            {SnappingManager.GetIsDragging() ? null : renderAnnotations()}
-                            {this.annotationLayer}
-                        </div>
-                    </div>
+                    {this.webpage}
                     {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
                         <div style={{ transformOrigin: 'top left', transform: `scale(${1 / scale})` }}>
                             <MarqueeAnnotator
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 2c83082b7..a45edfbca 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -9,6 +9,7 @@ import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
+import { DragManager } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SharingManager } from '../../util/SharingManager';
 import { SnappingManager } from '../../util/SnappingManager';
@@ -487,9 +488,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
     overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._viewScale, 1));
     panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
     panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
-    basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter('textInlineAnnotations')];
     transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
-    opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()];
+    opaqueFilter = () => [...this.props.docFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])];
     childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
             if (doc.textInlineAnnotations) return 'none';
@@ -498,56 +498,43 @@ export class PDFViewer extends React.Component<IViewerProps> {
         return this.props.styleProvider?.(doc, props, property);
     };
 
-    renderAnnotations = (docFilters?: () => string[], dontRender?: boolean) => (
-        <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}
-            bringToFront={emptyFunction}
-            docFilters={docFilters || this.basicFilter}
-            styleProvider={this.childStyleProvider}
-            dontRenderDocuments={dontRender}
-            CollectionView={undefined}
-            ScreenToLocalTransform={this.overlayTransform}
-            renderDepth={this.props.renderDepth + 1}
-        />
+    renderAnnotations = (docFilters?: () => string[], mixBlendMode?: any, display?: string) => (
+        <div
+            className="pdfViewerDash-overlay"
+            style={{
+                mixBlendMode: mixBlendMode,
+                display: display,
+                transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})`,
+            }}>
+            <CollectionFreeFormView
+                {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                renderDepth={this.props.renderDepth + 1}
+                isAnnotationOverlay={true}
+                fieldKey={this.props.fieldKey + '-annotations'}
+                CollectionView={undefined}
+                setPreviewCursor={this.setPreviewCursor}
+                PanelHeight={this.panelHeight}
+                PanelWidth={this.panelWidth}
+                ScreenToLocalTransform={this.overlayTransform}
+                dropAction={'alias'}
+                docFilters={docFilters}
+                select={emptyFunction}
+                bringToFront={emptyFunction}
+                styleProvider={this.childStyleProvider}
+            />
+        </div>
     );
     @computed get overlayTransparentAnnotations() {
-        return this.renderAnnotations(this.transparentFilter, false);
+        return this.renderAnnotations(this.transparentFilter, 'multiply', DragManager.docsBeingDragged.length ? 'none' : undefined);
     }
     @computed get overlayOpaqueAnnotations() {
-        return this.renderAnnotations(this.opaqueFilter, false);
-    }
-    @computed get overlayClickableAnnotations() {
-        return <div style={{ height: NumCast(this.props.rootDoc.scrollHeight) }}>{this.renderAnnotations(undefined, true)}</div>;
+        return this.renderAnnotations(this.opaqueFilter, this.allAnnotations.some(anno => anno.mixBlendMode) ? 'hard-light' : undefined);
     }
     @computed get overlayLayer() {
         return (
             <div style={{ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : 'none' }}>
-                <div
-                    className="pdfViewerDash-overlay"
-                    style={{
-                        pointerEvents: SnappingManager.GetIsDragging() ? 'all' : 'none',
-                        mixBlendMode: 'multiply',
-                        transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})`,
-                    }}>
-                    {this.overlayTransparentAnnotations}
-                </div>
-                <div
-                    className="pdfViewerDash-overlay"
-                    style={{
-                        pointerEvents: SnappingManager.GetIsDragging() ? 'all' : 'none',
-                        mixBlendMode: this.allAnnotations.some(anno => anno.mixBlendMode) ? 'hard-light' : undefined,
-                        transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})`,
-                    }}>
-                    {this.overlayOpaqueAnnotations}
-                    {this.overlayClickableAnnotations}
-                </div>
+                {this.overlayTransparentAnnotations}
+                {this.overlayOpaqueAnnotations}
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2

