From 0e3892e63758accd7dae274072ad7893934c3624 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 18 Oct 2022 15:07:31 -0400
Subject: cleaned up gestureutils a bit.  added recognize gestures option, but
 it probably shouldn't be used until the geometry gesture recognition
 descriptions are improved.

---
 src/client/views/nodes/button/FontIconBox.tsx | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 6d1751b25..6eaf3c31a 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -11,6 +11,7 @@ import { InkTool } from '../../../../fields/InkField';
 import { ScriptField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { WebField } from '../../../../fields/URLField';
+import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
 import { aggregateBounds, Utils } from '../../../../Utils';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
@@ -85,6 +86,12 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
     static SetShowLabels(show: boolean) {
         Doc.UserDoc()._showLabel = show;
     }
+    static GetRecognizeGestures() {
+        return BoolCast(Doc.UserDoc()._recognizeGestures);
+    }
+    static SetRecognizeGesturs(show: boolean) {
+        Doc.UserDoc()._recognizeGestures = show;
+    }
 
     // Determining UI Specs
     @computed get label() {
@@ -775,7 +782,7 @@ ScriptingGlobals.add(function setActiveTool(tool: string, checkResult?: boolean)
     if (checkResult) {
         return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool ? Colors.MEDIUM_BLUE : 'transparent';
     }
-    if (['circle', 'square', 'line'].includes(tool)) {
+    if ([GestureUtils.Gestures.Circle, GestureUtils.Gestures.Rectangle, GestureUtils.Gestures.Line, GestureUtils.Gestures.Triangle].includes(tool as any)) {
         if (GestureOverlay.Instance.InkShape === tool) {
             Doc.ActiveTool = InkTool.None;
             GestureOverlay.Instance.InkShape = InkTool.None;
-- 
cgit v1.2.3-70-g09d2


From a81ab2e6f75681bc4fb3a7b49d2056144e396b94 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 19 Oct 2022 12:38:19 -0400
Subject: fixed erasing straight line stroke segments caused by intersections
 with other strokes. cleaned up more with gestures.  changed docView lock icon
 to be part of docDecorations.

---
 src/client/views/DocumentDecorations.scss          | 28 ++++++-
 src/client/views/DocumentDecorations.tsx           | 41 +++++++++-
 src/client/views/GestureOverlay.tsx                | 21 +++---
 src/client/views/StyleProvider.scss                | 18 ++---
 src/client/views/StyleProvider.tsx                 |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 58 ++++++--------
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/ImageBox.tsx                |  5 +-
 src/client/views/nodes/button/FontIconBox.tsx      | 20 ++---
 src/pen-gestures/GestureUtils.ts                   |  2 -
 src/pen-gestures/ndollar.ts                        | 88 +++++++++-------------
 11 files changed, 148 insertions(+), 137 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 5c8dd36cc..4e0b061a6 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -1,7 +1,7 @@
 @import 'global/globalCssVariables';
 
 $linkGap: 3px;
-$headerHeight: 20px;
+$headerHeight: 25px;
 $resizeHandler: 8px;
 
 .documentDecorations-Dark,
@@ -54,6 +54,8 @@ $resizeHandler: 8px;
         grid-column-end: 4;
         flex-direction: row;
         gap: 2px;
+        pointer-events: all;
+        cursor: move;
 
         .documentDecorations-openButton {
             display: flex;
@@ -236,8 +238,8 @@ $resizeHandler: 8px;
     .documentDecorations-borderRadius {
         position: absolute;
         border-radius: 100%;
-        left: 3px;
-        top: 23px;
+        left: 7px;
+        top: 27px;
         background: $medium-gray;
         height: 10;
         width: 10;
@@ -245,6 +247,21 @@ $resizeHandler: 8px;
         cursor: nwse-resize;
     }
 
+    .documentDecorations-lock {
+        position: absolute;
+        background: black;
+        right: 11;
+        top: 30px;
+        color: gray;
+        height: 14;
+        width: 14;
+        pointer-events: all;
+        margin: auto;
+        display: flex;
+        align-items: center;
+        flex-direction: column;
+    }
+
     .documentDecorations-rotationPath {
         position: absolute;
         width: 100%;
@@ -260,6 +277,7 @@ $resizeHandler: 8px;
         cursor: nwse-resize;
         background: unset;
         opacity: 1;
+        transform: scale(2);
     }
 
     .documentDecorations-topLeftResizer {
@@ -314,6 +332,7 @@ $resizeHandler: 8px;
     .documentDecorations-topLeftResizer:hover,
     .documentDecorations-bottomRightResizer:hover {
         opacity: 1;
+        background: black;
     }
 
     .documentDecorations-bottomRightResizer {
@@ -325,6 +344,7 @@ $resizeHandler: 8px;
         cursor: nesw-resize;
         background: unset;
         opacity: 1;
+        transform: scale(2);
     }
 
     .documentDecorations-topRightResizer {
@@ -339,7 +359,6 @@ $resizeHandler: 8px;
 
     .documentDecorations-topRightResizer:hover,
     .documentDecorations-bottomLeftResizer:hover {
-        cursor: nesw-resize;
         background: black;
         opacity: 1;
     }
@@ -392,6 +411,7 @@ $resizeHandler: 8px;
     justify-content: center;
     align-items: center;
     gap: 5px;
+    top: 4px;
     background: $light-gray;
 }
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 9881ef9f1..6cf7df357 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -140,6 +140,16 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         }
     };
 
+    @action onContainerDown = (e: React.PointerEvent): void => {
+        setupMoveUpEvents(
+            this,
+            e,
+            e => this.onBackgroundMove(true, e),
+            e => {},
+            emptyFunction
+        );
+    };
+
     @action onTitleDown = (e: React.PointerEvent): void => {
         setupMoveUpEvents(
             this,
@@ -311,6 +321,27 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         );
     };
 
+    @action
+    onLockDown = (e: React.PointerEvent): void => {
+        // Call util move event function
+        setupMoveUpEvents(
+            this, // target
+            e, // pointerEvent
+            returnFalse, // moveEvent
+            emptyFunction, // upEvent
+            e => {
+                UndoManager.RunInBatch(
+                    () =>
+                        SelectionManager.Views().map(dv => {
+                            dv.rootDoc._lockedPosition = !dv.rootDoc._lockedPosition;
+                            dv.rootDoc._pointerEvents = dv.rootDoc._lockedPosition ? 'none' : undefined;
+                        }),
+                    'toggleBackground'
+                );
+            } // clickEvent
+        );
+    };
+
     setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => {
         const newloccentern = seldocview.props.ScreenToLocalTransform().transformPoint(rotCenter[0], rotCenter[1]);
         const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2];
@@ -791,7 +822,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                                 width: bounds.r - bounds.x + this._resizeBorderWidth + 'px',
                                 height: bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight + 'px',
                             }}>
-                            <div className="documentDecorations-topbar">
+                            <div className="documentDecorations-topbar" onPointerDown={this.onContainerDown}>
                                 {hideDeleteButton ? <div /> : topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')}
                                 {hideResizers || hideDeleteButton ? <div /> : topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')}
                                 {hideResizers ? <div /> : titleArea}
@@ -817,21 +848,23 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                                     key="rad"
                                     style={{
                                         background: `${this._isRounding ? Colors.MEDIUM_BLUE : undefined}`,
-                                        left: `${radiusHandleLocation + 3}`,
-                                        top: `${radiusHandleLocation + 23}`,
+                                        transform: `translate(${radiusHandleLocation}px, ${radiusHandleLocation}px)`,
                                     }}
                                     className={`documentDecorations-borderRadius`}
                                     onPointerDown={this.onRadiusDown}
                                     onContextMenu={e => e.preventDefault()}
                                 />
                             )}
+                            <div key="lock" className="documentDecorations-lock" style={{ color: seldocview.rootDoc._lockedPosition ? 'red' : undefined }} onPointerDown={this.onLockDown} onContextMenu={e => e.preventDefault()}>
+                                <FontAwesomeIcon size="sm" icon="lock" />
+                            </div>
 
                             {hideDocumentButtonBar ? null : (
                                 <div
                                     className="link-button-container"
                                     key="links"
                                     style={{
-                                        transform: ` translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
+                                        transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
                                     }}>
                                     <DocumentButtonBar views={SelectionManager.Views} />
                                 </div>
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 23b03bc50..362aa3c86 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -1,7 +1,7 @@
 import React = require('react');
 import * as fitCurve from 'fit-curve';
 import { action, computed, observable, runInAction, trace } from 'mobx';
-import { Doc } from '../../fields/Doc';
+import { Doc, Opt } from '../../fields/Doc';
 import { InkData, InkTool } from '../../fields/InkField';
 import { List } from '../../fields/List';
 import { ScriptField } from '../../fields/ScriptField';
@@ -49,7 +49,7 @@ interface GestureOverlayProps {
 export class GestureOverlay extends Touchable<GestureOverlayProps> {
     static Instance: GestureOverlay;
 
-    @observable public InkShape: string = '';
+    @observable public InkShape: Opt<GestureUtils.Gestures>;
     @observable public SavedColor?: string;
     @observable public SavedWidth?: number;
     @observable public Tool: ToolglassTools = ToolglassTools.None;
@@ -644,8 +644,6 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
         if (this._points.length > 1) {
             const B = this.svgBounds;
             const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
-            //push first points to so interactionUtil knows pointer is up
-            this._points.push({ X: this._points[0].X, Y: this._points[0].Y });
 
             const initialPoint = this._points[0];
             const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height;
@@ -685,10 +683,10 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
             //if any of the shape is activated in the CollectionFreeFormViewChrome
             else if (this.InkShape) {
                 this.makeBezierPolygon(this.InkShape, false);
-                this.dispatchGesture(GestureUtils.Gestures.Stroke);
+                this.dispatchGesture(this.InkShape);
                 this._points.length = 0;
                 if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) {
-                    this.InkShape = '';
+                    this.InkShape = undefined;
                     Doc.ActiveTool = InkTool.None;
                 }
             }
@@ -703,12 +701,13 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                         case GestureUtils.Gestures.Rectangle:
                         case GestureUtils.Gestures.Circle:
                             this.makeBezierPolygon(result.Name, true);
-                        case GestureUtils.Gestures.StartBracket:
-                        case GestureUtils.Gestures.EndBracket:
                             actionPerformed = this.dispatchGesture(result.Name);
                             break;
                         case GestureUtils.Gestures.Line:
-                            actionPerformed = this.handleLineGesture();
+                            if (!(actionPerformed = this.handleLineGesture())) {
+                                this.makeBezierPolygon(result.Name, true);
+                                actionPerformed = this.dispatchGesture(GestureUtils.Gestures.Stroke);
+                            }
                             break;
                         case GestureUtils.Gestures.Scribble:
                             console.log('scribble');
@@ -972,7 +971,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                                 ActiveDash(),
                                 1,
                                 1,
-                                this.InkShape,
+                                this.InkShape ?? '',
                                 'none',
                                 1.0,
                                 false
@@ -999,7 +998,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                             ActiveDash(),
                             1,
                             1,
-                            this.InkShape,
+                            this.InkShape ?? '',
                             'none',
                             1.0,
                             false
diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss
index 8929954c8..b1c97164a 100644
--- a/src/client/views/StyleProvider.scss
+++ b/src/client/views/StyleProvider.scss
@@ -1,11 +1,11 @@
 .styleProvider-lock {
-    font-size: 12px;
-    width: 20; 
-    height: 20; 
-    position: absolute; 
-    right: -25; 
-    top: -5;
-    background: transparent;
+    font-size: 10;
+    width: 15;
+    height: 15;
+    position: absolute;
+    right: -0;
+    top: 0;
+    background: black;
     pointer-events: all;
     opacity: 0.3;
     display: flex;
@@ -15,7 +15,7 @@
     cursor: default;
 }
 .styleProvider-lock:hover {
-    opacity:1;
+    opacity: 1;
 }
 
 .styleProvider-treeView-icon,
@@ -26,4 +26,4 @@
 
 .styleProvider-treeView-icon {
     opacity: 0;
-}
\ No newline at end of file
+}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index aadcd7169..a5a886f42 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -276,7 +276,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.Decorations:
             if (props?.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform || doc?.x !== undefined || doc?.y !== undefined) {
                 return doc &&
-                    (isBackground() || selected) &&
+                    isBackground() &&
                     !Doc.IsSystem(doc) &&
                     (props?.renderDepth || 0) > 0 &&
                     ((doc.type === DocumentType.COL && doc._viewType !== CollectionViewType.Pile) || [DocumentType.RTF, DocumentType.IMG, DocumentType.INK].includes(doc.type as DocumentType)) ? (
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index daf69d4f6..983cf4f9b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -568,6 +568,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @undoBatch
     onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
         switch (ge.gesture) {
+            case GestureUtils.Gestures.Line:
+                break;
             default:
             case GestureUtils.Gestures.Circle:
             case GestureUtils.Gestures.Rectangle:
@@ -602,33 +604,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 e.stopPropagation();
                 break;
             case GestureUtils.Gestures.Rectangle:
-                const lt = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y)));
-                const rb = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
-                const bounds = { x: lt[0], r: rb[0], y: lt[1], b: rb[1] };
-                const bWidth = bounds.r - bounds.x;
-                const bHeight = bounds.b - bounds.y;
-                const sel = this.getActiveDocuments().filter(doc => {
-                    const l = NumCast(doc.x);
-                    const r = l + doc[WidthSym]();
-                    const t = NumCast(doc.y);
-                    const b = t + doc[HeightSym]();
-                    const pass = !(bounds.x > r || bounds.r < l || bounds.y > b || bounds.b < t);
-                    if (pass) {
-                        doc.x = l - bounds.x - bWidth / 2;
-                        doc.y = t - bounds.y - bHeight / 2;
-                    }
-                    return pass;
-                });
-                this.addDocument(Docs.Create.FreeformDocument(sel, { title: 'nested collection', x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 }));
-                sel.forEach(d => this.props.removeDocument?.(d));
-                e.stopPropagation();
-                break;
-            case GestureUtils.Gestures.StartBracket:
-                const start = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y)));
-                this._inkToTextStartX = start[0];
-                this._inkToTextStartY = start[1];
-                break;
-            case GestureUtils.Gestures.EndBracket:
                 if (this._inkToTextStartX && this._inkToTextStartY) {
                     const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
                     const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === 'rtf' && s.color);
@@ -805,15 +780,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint);
                 const currPointInkSpace = inkStroke.ptFromScreen(currPoint);
                 for (var i = 0; i < inkData.length - 3; i += 4) {
-                    const intersects = Array.from(
-                        new Set(
-                            InkField.Segment(inkData, i).intersects({
-                                // compute all unique intersections
-                                p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y },
-                                p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y },
-                            }) as (number | string)[]
-                        )
-                    ); // convert to more manageable union array type
+                    const rawIntersects = InkField.Segment(inkData, i).intersects({
+                        // compute all unique intersections
+                        p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y },
+                        p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y },
+                    });
+                    const intersects = Array.from(new Set(rawIntersects as (number | string)[])); // convert to more manageable union array type
+                    if (intersects.length) {
+                        console.log();
+                    }
                     // return tuples of the inkingStroke intersected, and the t value of the intersection
                     intersections.push(...intersects.map(t => ({ inkView, t: +t + Math.floor(i / 4) }))); // convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve
                 }
@@ -862,6 +837,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return segments;
     };
 
+    // for some reason bezier.js doesn't handle the case of intersecting a linear curve, so we wrap the intersection
+    // call in a test for linearity
+    bintersects = (curve: Bezier, otherCurve: Bezier) => {
+        if ((otherCurve as any)._linear) {
+            return curve.lineIntersects({ p1: otherCurve.points[0], p2: otherCurve.points[3] });
+        }
+        return curve.intersects(otherCurve);
+    };
+
     /**
      * Determines all possible intersections of the current curve of the intersected ink stroke with all other curves of all
      * ink strokes in the current collection.
@@ -886,7 +870,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     if (ink?.Document === otherInk.props.Document && neighboringSegment) continue;
 
                     const otherCurve = new Bezier(otherCtrlPts.slice(j, j + 4).map(p => ({ x: p.X, y: p.Y })));
-                    curve.intersects(otherCurve).forEach((val: string | number, i: number) => {
+                    this.bintersects(curve, otherCurve).forEach((val: string | number, i: number) => {
                         // Converting the Bezier.js Split type to a t-value number.
                         const t = +val.toString().split('/')[0];
                         if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack!  don't know why but intersection points are doubled from bezier.js (but not identical).
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1c48d47e9..040e03150 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1082,7 +1082,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     <DocumentLinksButton
                         View={this.props.DocumentView()}
                         scaling={this.linkButtonInverseScaling}
-                        Offset={[this.topMost ? 0 : !this.props.isSelected() ? -15 : -36, undefined, undefined, this.topMost ? 10 : !this.props.isSelected() ? -15 : -28]}
+                        Offset={[this.topMost ? 0 : !this.props.isSelected() ? -15 : -36, undefined, undefined, this.topMost ? 10 : !this.props.isSelected() ? -15 : -32]}
                     />
                 )}
                 {audioView}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 959c641a8..461d6984d 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -8,7 +8,7 @@ import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
 import { createSchema } from '../../../fields/Schema';
 import { ComputedField } from '../../../fields/ScriptField';
-import { Cast, NumCast } from '../../../fields/Types';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
@@ -77,10 +77,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             ({ forceFull, scrSize, selected }) => (this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 ? '_l' : '_o'),
             { fireImmediately: true, delay: 1000 }
         );
+        const layoutDoc = this.layoutDoc;
         this._disposers.path = reaction(
             () => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }),
             ({ nativeSize, width }) => {
-                if (true || !this.layoutDoc._height) {
+                if (layoutDoc === this.layoutDoc || !this.layoutDoc._height) {
                     this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth;
                 }
             },
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 6eaf3c31a..fd0c0d141 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -771,36 +771,26 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
     CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
 }
 
-/** INK
- *      setActiveTool
- *      setStrokeWidth
- *      setStrokeColor
- **/
-
-ScriptingGlobals.add(function setActiveTool(tool: string, checkResult?: boolean) {
+ScriptingGlobals.add(function setActiveTool(tool: InkTool | GestureUtils.Gestures, checkResult?: boolean) {
     InkTranscription.Instance?.createInkGroup();
     if (checkResult) {
         return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool ? Colors.MEDIUM_BLUE : 'transparent';
     }
-    if ([GestureUtils.Gestures.Circle, GestureUtils.Gestures.Rectangle, GestureUtils.Gestures.Line, GestureUtils.Gestures.Triangle].includes(tool as any)) {
+    if (Object.values(GestureUtils.Gestures).includes(tool as any)) {
         if (GestureOverlay.Instance.InkShape === tool) {
             Doc.ActiveTool = InkTool.None;
-            GestureOverlay.Instance.InkShape = InkTool.None;
+            GestureOverlay.Instance.InkShape = undefined;
         } else {
             Doc.ActiveTool = InkTool.Pen;
-            GestureOverlay.Instance.InkShape = tool;
+            GestureOverlay.Instance.InkShape = tool as GestureUtils.Gestures;
         }
     } else if (tool) {
         // pen or eraser
         if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape) {
             Doc.ActiveTool = InkTool.None;
-        } else if (tool == InkTool.Write) {
-            // console.log("write mode selected - create groupDoc here!", tool)
-            Doc.ActiveTool = tool;
-            GestureOverlay.Instance.InkShape = '';
         } else {
             Doc.ActiveTool = tool as any;
-            GestureOverlay.Instance.InkShape = '';
+            GestureOverlay.Instance.InkShape = undefined;
         }
     } else {
         Doc.ActiveTool = InkTool.None;
diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts
index 2d3b1fdb8..41917aac9 100644
--- a/src/pen-gestures/GestureUtils.ts
+++ b/src/pen-gestures/GestureUtils.ts
@@ -19,8 +19,6 @@ export namespace GestureUtils {
 
     export enum Gestures {
         Line = 'line',
-        StartBracket = 'startbracket',
-        EndBracket = 'endbracket',
         Stroke = 'stroke',
         Scribble = 'scribble',
         Text = 'text',
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index b10a9da17..3ee9506cb 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -145,7 +145,7 @@ export class Result {
 //
 // NDollarRecognizer constants
 //
-const NumMultistrokes = 6;
+let NumMultistrokes = 0;
 const NumPoints = 96;
 const SquareSize = 250.0;
 const OneDThreshold = 0.25; // customize to desired gesture set (usually 0.20 - 0.35)
@@ -162,73 +162,59 @@ const AngleSimilarityThreshold = Deg2Rad(30.0);
 // NDollarRecognizer class
 //
 export class NDollarRecognizer {
-    public Multistrokes: Multistroke[];
+    public Multistrokes: Multistroke[] = [];
 
-    /**
-     * @IMPORTANT - IF YOU'RE ADDING A NEW GESTURE, BE SURE TO INCREMENT THE NumMultiStrokes CONST RIGHT ABOVE THIS CLASS.
-     */
     constructor(
         useBoundedRotationInvariance: boolean // constructor
     ) {
         //
         // one predefined multistroke for each multistroke type
         //
-        this.Multistrokes = new Array(NumMultistrokes);
-        this.Multistrokes[0] = new Multistroke(
-            GestureUtils.Gestures.Rectangle,
-            useBoundedRotationInvariance,
-            new Array(
+        this.Multistrokes.push(
+            new Multistroke(
+                GestureUtils.Gestures.Rectangle,
+                useBoundedRotationInvariance,
                 new Array(
-                    new Point(30, 146), //new Point(29, 160), new Point(30, 180), new Point(31, 200),
-                    new Point(30, 222), //new Point(50, 219), new Point(70, 225), new Point(90, 230),
-                    new Point(106, 225), //new Point(100, 200), new Point(106, 180), new Point(110, 160),
-                    new Point(106, 146), //new Point(80, 150), new Point(50, 146),
-                    new Point(30, 143)
+                    new Array(
+                        new Point(30, 146), //new Point(29, 160), new Point(30, 180), new Point(31, 200),
+                        new Point(30, 222), //new Point(50, 219), new Point(70, 225), new Point(90, 230),
+                        new Point(106, 225), //new Point(100, 200), new Point(106, 180), new Point(110, 160),
+                        new Point(106, 146), //new Point(80, 150), new Point(50, 146),
+                        new Point(30, 143)
+                    )
                 )
             )
         );
-        this.Multistrokes[1] = new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array(new Array(new Point(12, 347), new Point(119, 347))));
-        this.Multistrokes[2] = new Multistroke(
-            GestureUtils.Gestures.StartBracket,
-            useBoundedRotationInvariance,
-            new Array(
-                // new Array(new Point(145, 20), new Point(30, 21), new Point(34, 150))
-                new Array(new Point(31, 25), new Point(145, 20), new Point(31, 25), new Point(34, 150))
+        this.Multistrokes.push(new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array(new Array(new Point(12, 347), new Point(119, 347)))));
+        this.Multistrokes.push(
+            new Multistroke(
+                GestureUtils.Gestures.Triangle, // equilateral
+                useBoundedRotationInvariance,
+                new Array(new Array(new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100)))
             )
         );
-        this.Multistrokes[3] = new Multistroke(
-            GestureUtils.Gestures.EndBracket,
-            useBoundedRotationInvariance,
-            new Array(
-                // new Array(new Point(150, 21), new Point(149, 150), new Point(26, 152))
-                // new Array(new Point(150, 150), new Point(150, 0), new Point(150, 150), new Point(0, 150))
-                new Array(new Point(10, 100), new Point(100, 100), new Point(150, 12), new Point(200, 103), new Point(300, 100))
-            )
-        );
-        this.Multistrokes[4] = new Multistroke(
-            GestureUtils.Gestures.Triangle, // equilateral
-            useBoundedRotationInvariance,
-            new Array(new Array(new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100)))
-        );
-        this.Multistrokes[5] = new Multistroke(
-            GestureUtils.Gestures.Circle,
-            useBoundedRotationInvariance,
-            new Array(
+        this.Multistrokes.push(
+            new Multistroke(
+                GestureUtils.Gestures.Circle,
+                useBoundedRotationInvariance,
                 new Array(
-                    new Point(200, 250),
-                    new Point(240, 230),
-                    new Point(248, 210),
-                    new Point(248, 190),
-                    new Point(240, 170),
-                    new Point(200, 150),
-                    new Point(160, 170),
-                    new Point(151, 190),
-                    new Point(151, 210),
-                    new Point(160, 230),
-                    new Point(201, 250)
+                    new Array(
+                        new Point(200, 250),
+                        new Point(240, 230),
+                        new Point(248, 210),
+                        new Point(248, 190),
+                        new Point(240, 170),
+                        new Point(200, 150),
+                        new Point(160, 170),
+                        new Point(151, 190),
+                        new Point(151, 210),
+                        new Point(160, 230),
+                        new Point(201, 250)
+                    )
                 )
             )
         );
+        NumMultistrokes = this.Multistrokes.length; // NumMultistrokes flags the end of the non user-defined gstures strokes
         //
         // PREDEFINED STROKES
         //
-- 
cgit v1.2.3-70-g09d2


From 5df8e9041e7a0e8ac98f0c911a2b603c0d8e89d3 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 20 Oct 2022 11:07:04 -0400
Subject: added double-tap to keep ink primitive mode.  cleaned up circle
 drawing and some other gesture code.

---
 src/client/util/CurrentUserUtils.ts                | 14 ++---
 src/client/util/InteractionUtils.tsx               |  6 +-
 src/client/views/GestureOverlay.tsx                | 69 +++++++---------------
 src/client/views/collections/CollectionMenu.tsx    |  7 ++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  3 +-
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/button/FontIconBox.tsx      | 51 +++++++++-------
 7 files changed, 68 insertions(+), 84 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index bfa868dbc..2bc464127 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -46,7 +46,7 @@ interface Button {
     buttonText?: string;
 
     // fields that do not correspond to DocumentOption fields
-    scripts?: { script?: string; onClick?: string; }
+    scripts?: { script?: string; onClick?: string; onDoubleClick?: string }
     funcs?: { [key:string]: string };
     subMenu?: Button[];
 }
@@ -628,13 +628,13 @@ export class CurrentUserUtils {
 
     static inkTools():Button[] {
         return [
-            { title: "Pen",    toolTip: "Pen (Ctrl+P)",          btnType: ButtonType.ToggleButton, icon: "pen-nib",                     scripts: {onClick:'{ return setActiveTool("pen", _readOnly_);}' }},
-            { title: "Write",  toolTip: "Write (Ctrl+Shift+P)",  btnType: ButtonType.ToggleButton, icon: "pen",                         scripts: {onClick:'{ return setActiveTool("write", _readOnly_);}'} },
-            { title: "Eraser", toolTip: "Eraser (Ctrl+E)",       btnType: ButtonType.ToggleButton, icon: "eraser",                      scripts: {onClick:'{ return setActiveTool("eraser", _readOnly_);}' }},
+            { title: "Pen",    toolTip: "Pen (Ctrl+P)",          btnType: ButtonType.ToggleButton, icon: "pen-nib",                     scripts: {onClick:'{ return setActiveTool("pen", false, _readOnly_);}' }},
+            { title: "Write",  toolTip: "Write (Ctrl+Shift+P)",  btnType: ButtonType.ToggleButton, icon: "pen",                         scripts: {onClick:'{ return setActiveTool("write", false, _readOnly_);}'} },
+            { title: "Eraser", toolTip: "Eraser (Ctrl+E)",       btnType: ButtonType.ToggleButton, icon: "eraser",                      scripts: {onClick:'{ return setActiveTool("eraser", false, _readOnly_);}' }},
             // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveTool("highlighter")'} },
-            { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle",                      scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", _readOnly_);}`} },
-            { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square",                      scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", _readOnly_);}`} },
-            { title: "Line",   toolTip: "Line (Ctrl+Shift+L)",   btnType: ButtonType.ToggleButton, icon: "minus",                       scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", _readOnly_);}`} },
+            { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle",                      scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", true, _readOnly_);}`} },
+            { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square",                      scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", true, _readOnly_);}`} },
+            { title: "Line",   toolTip: "Line (double tap to lock mode)",   btnType: ButtonType.ToggleButton, icon: "minus",                       scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", true, _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},
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 6622b498d..3cdf4dbd2 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -242,16 +242,16 @@ export namespace InteractionUtils {
                 const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
                 const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
                 const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
-                for (var x = Math.min(left, right); x < Math.max(left, right); x++) {
+                for (var x = centerX - radius; x < centerX + radius; x++) {
                     const y = Math.sqrt(Math.pow(radius, 2) - Math.pow(x - centerX, 2)) + centerY;
                     points.push({ X: x, Y: y });
                 }
-                for (var x = Math.max(left, right); x > Math.min(left, right); x--) {
+                for (var x = centerX + radius; x > centerX - radius; x--) {
                     const y = Math.sqrt(Math.pow(radius, 2) - Math.pow(x - centerX, 2)) + centerY;
                     const newY = centerY - (y - centerY);
                     points.push({ X: x, Y: newY });
                 }
-                points.push({ X: Math.min(left, right), Y: Math.sqrt(Math.pow(radius, 2) - Math.pow(Math.min(left, right) - centerX, 2)) + centerY });
+                points.push({ X: centerX - radius, Y: Math.sqrt(Math.pow(radius, 2) - Math.pow(-radius, 2)) + centerY });
                 break;
 
             case GestureUtils.Gestures.Line:
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 362aa3c86..a29073f14 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -16,7 +16,6 @@ import { InteractionUtils } from '../util/InteractionUtils';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
 import { SelectionManager } from '../util/SelectionManager';
 import { Transform } from '../util/Transform';
-import { CollectionFreeFormViewChrome } from './collections/CollectionMenu';
 import './GestureOverlay.scss';
 import {
     ActiveArrowEnd,
@@ -53,6 +52,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
     @observable public SavedColor?: string;
     @observable public SavedWidth?: number;
     @observable public Tool: ToolglassTools = ToolglassTools.None;
+    @observable public KeepPrimitiveMode = false; // for whether primitive selection enters a one-shot or persistent mode
 
     @observable private _thumbX?: number;
     @observable private _thumbY?: number;
@@ -616,29 +616,16 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
         return false;
     };
 
-    handleLineGesture = (): boolean => {
-        const actionPerformed = false;
-        const B = this.svgBounds;
-
-        // get the two targets at the ends of the line
-        const ep1 = this._points[0];
-        const ep2 = this._points.lastElement();
-        const target1 = document.elementFromPoint(ep1.X, ep1.Y);
-        const target2 = document.elementFromPoint(ep2.X, ep2.Y);
-
-        const ge = new CustomEvent<GestureUtils.GestureEvent>('dashOnGesture', {
-            bubbles: true,
-            detail: {
-                points: this._points.slice(),
-                gesture: GestureUtils.Gestures.Line,
-                bounds: B,
-            },
-        });
-        target1?.dispatchEvent(ge);
-        target2?.dispatchEvent(ge);
-        return actionPerformed;
-    };
-
+    @action primCreated() {
+        if (!this.KeepPrimitiveMode) {
+            this.InkShape = undefined;
+            //get out of ink mode after each stroke=
+            //if (Doc.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor);
+            Doc.ActiveTool = InkTool.None;
+            // SetActiveArrowStart('none');
+            // SetActiveArrowEnd('none');
+        }
+    }
     @action
     onPointerUp = (e: PointerEvent) => {
         if (this._points.length > 1) {
@@ -654,7 +641,6 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                 switch (this.Tool) {
                     case ToolglassTools.InkToText:
                         this._strokes.push(this._points.slice());
-                        this._points.length = 0;
                         CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then(results => {
                             const wordResults = results.filter((r: any) => r.category === 'line');
                             const possibilities: string[] = [];
@@ -676,7 +662,6 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                         break;
                     case ToolglassTools.IgnoreGesture:
                         this.dispatchGesture(GestureUtils.Gestures.Stroke);
-                        this._points.length = 0;
                         break;
                 }
             }
@@ -684,11 +669,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
             else if (this.InkShape) {
                 this.makeBezierPolygon(this.InkShape, false);
                 this.dispatchGesture(this.InkShape);
-                this._points.length = 0;
-                if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) {
-                    this.InkShape = undefined;
-                    Doc.ActiveTool = InkTool.None;
-                }
+                this.primCreated();
             }
             // if we're not drawing in a toolglass try to recognize as gesture
             else {
@@ -697,18 +678,13 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                 let actionPerformed = false;
                 if (Doc.UserDoc().recognizeGestures && result && result.Score > 0.7) {
                     switch (result.Name) {
+                        case GestureUtils.Gestures.Line:
                         case GestureUtils.Gestures.Triangle:
                         case GestureUtils.Gestures.Rectangle:
                         case GestureUtils.Gestures.Circle:
                             this.makeBezierPolygon(result.Name, true);
                             actionPerformed = this.dispatchGesture(result.Name);
                             break;
-                        case GestureUtils.Gestures.Line:
-                            if (!(actionPerformed = this.handleLineGesture())) {
-                                this.makeBezierPolygon(result.Name, true);
-                                actionPerformed = this.dispatchGesture(GestureUtils.Gestures.Stroke);
-                            }
-                            break;
                         case GestureUtils.Gestures.Scribble:
                             console.log('scribble');
                             break;
@@ -741,12 +717,9 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                     // TODO: nda - check inks to group here
                     checkInksToGroup();
                 }
-                this._points.length = 0;
             }
-        } else {
-            this._points.length = 0;
         }
-        CollectionFreeFormViewChrome.Instance?.primCreated();
+        this._points.length = 0;
     };
 
     makeBezierPolygon = (shape: string, gesture: boolean) => {
@@ -901,17 +874,17 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
         return false;
     };
 
-    dispatchGesture = (gesture: GestureUtils.Gestures, stroke?: InkData, data?: any) => {
-        const target = document.elementFromPoint((stroke ?? this._points)[0].X, (stroke ?? this._points)[0].Y);
+    dispatchGesture = (gesture: GestureUtils.Gestures, stroke?: InkData, text?: any) => {
+        const points = (stroke ?? this._points).slice();
         return (
-            target?.dispatchEvent(
+            document.elementFromPoint(points[0].X, points[0].Y)?.dispatchEvent(
                 new CustomEvent<GestureUtils.GestureEvent>('dashOnGesture', {
                     bubbles: true,
                     detail: {
-                        points: stroke ?? this._points.slice(),
-                        gesture: gesture as any,
-                        bounds: this.getBounds(stroke ?? this._points),
-                        text: data,
+                        points,
+                        gesture,
+                        bounds: this.getBounds(points),
+                        text,
                     },
                 })
             ) || false
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 46e8494ab..db81f28f6 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -40,6 +40,7 @@ import { CollectionLinearView } from './collectionLinear';
 import './CollectionMenu.scss';
 import { COLLECTION_BORDER_WIDTH } from './CollectionView';
 import { TabDocView } from './TabDocView';
+import { GestureUtils } from '../../../pen-gestures/GestureUtils';
 
 interface CollectionMenuProps {
     panelHeight: () => number;
@@ -767,7 +768,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
     private _draw = ['∿', '=', '⎯', '→', '↔︎', 'ロ', 'O'];
     private _head = ['', '', '', '', 'arrow', '', ''];
     private _end = ['', '', '', 'arrow', 'arrow', '', ''];
-    private _shapePrims = ['', '', 'line', 'line', 'line', 'rectangle', 'circle'];
+    private _shapePrims = ['', '', 'line', 'line', 'line', 'rectangle', 'circle'] as GestureUtils.Gestures[];
     private _title = ['pen', 'highlighter', 'line', 'line with arrow', 'line with double arrows', 'square', 'circle'];
     private _faName = ['pen-fancy', 'highlighter', 'minus', 'long-arrow-alt-right', 'arrows-alt-h', 'square', 'circle'];
     @observable _selectedPrimitive = this._shapePrims.length;
@@ -848,13 +849,13 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
                 SetActiveArrowEnd(this._end[i]);
                 SetActiveBezierApprox('300');
 
-                GestureOverlay.Instance.InkShape = this._shapePrims[i];
+                if (GestureOverlay.Instance) GestureOverlay.Instance.InkShape = this._shapePrims[i];
             } else {
                 this._selectedPrimitive = this._shapePrims.length;
                 Doc.ActiveTool = InkTool.None;
                 SetActiveArrowStart('');
                 SetActiveArrowEnd('');
-                GestureOverlay.Instance.InkShape = '';
+                if (GestureOverlay.Instance) GestureOverlay.Instance.InkShape = undefined;
                 SetActiveBezierApprox('0');
             }
             e.stopPropagation();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 983cf4f9b..7b93408c3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -568,9 +568,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @undoBatch
     onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
         switch (ge.gesture) {
-            case GestureUtils.Gestures.Line:
-                break;
             default:
+            case GestureUtils.Gestures.Line:
             case GestureUtils.Gestures.Circle:
             case GestureUtils.Gestures.Rectangle:
             case GestureUtils.Gestures.Triangle:
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 040e03150..16b5809fa 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -655,7 +655,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
         this._downX = e.clientX;
         this._downY = e.clientY;
-        if (Doc.ActiveTool === InkTool.None && !(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))) {
+        if ((Doc.ActiveTool === InkTool.None || this.props.addDocTab === returnFalse) && !(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))) {
             // if this is part of a template, let the event go up to the tempalte root unless right/ctrl clicking
             if (
                 (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) &&
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index fd0c0d141..883c4460b 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -771,31 +771,42 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
     CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
 }
 
-ScriptingGlobals.add(function setActiveTool(tool: InkTool | GestureUtils.Gestures, checkResult?: boolean) {
+function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, checkResult?: boolean) {
     InkTranscription.Instance?.createInkGroup();
     if (checkResult) {
-        return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool ? Colors.MEDIUM_BLUE : 'transparent';
-    }
-    if (Object.values(GestureUtils.Gestures).includes(tool as any)) {
-        if (GestureOverlay.Instance.InkShape === tool) {
-            Doc.ActiveTool = InkTool.None;
-            GestureOverlay.Instance.InkShape = undefined;
-        } else {
-            Doc.ActiveTool = InkTool.Pen;
-            GestureOverlay.Instance.InkShape = tool as GestureUtils.Gestures;
+        return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool
+            ? GestureOverlay.Instance?.KeepPrimitiveMode || ![GestureUtils.Gestures.Circle, GestureUtils.Gestures.Line, GestureUtils.Gestures.Rectangle].includes(tool as GestureUtils.Gestures)
+                ? Colors.MEDIUM_BLUE
+                : Colors.MEDIUM_BLUE_ALT
+            : 'transparent';
+    }
+    runInAction(() => {
+        if (GestureOverlay.Instance) {
+            GestureOverlay.Instance.KeepPrimitiveMode = keepPrim;
         }
-    } else if (tool) {
-        // pen or eraser
-        if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape) {
-            Doc.ActiveTool = InkTool.None;
+        if (Object.values(GestureUtils.Gestures).includes(tool as any)) {
+            if (GestureOverlay.Instance.InkShape === tool) {
+                Doc.ActiveTool = InkTool.None;
+                GestureOverlay.Instance.InkShape = undefined;
+            } else {
+                Doc.ActiveTool = InkTool.Pen;
+                GestureOverlay.Instance.InkShape = tool as GestureUtils.Gestures;
+            }
+        } else if (tool) {
+            // pen or eraser
+            if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape) {
+                Doc.ActiveTool = InkTool.None;
+            } else {
+                Doc.ActiveTool = tool as any;
+                GestureOverlay.Instance.InkShape = undefined;
+            }
         } else {
-            Doc.ActiveTool = tool as any;
-            GestureOverlay.Instance.InkShape = undefined;
+            Doc.ActiveTool = InkTool.None;
         }
-    } else {
-        Doc.ActiveTool = InkTool.None;
-    }
-});
+    });
+}
+
+ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode');
 
 // toggle: Set overlay status of selected document
 ScriptingGlobals.add(function setIsInkMask(checkResult?: boolean) {
-- 
cgit v1.2.3-70-g09d2


From 627c2aa72f61a888866840810a7d31cb0648437b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 28 Oct 2022 14:14:05 -0400
Subject: fixed proxyfields to use cache if it's available which saves orders
 of magnitude opening up Files list.   Fixed copying docs to appear in Files
 list.  fixed undo for change perspective and header color

---
 src/client/views/MainView.tsx                 |  7 +--
 src/client/views/collections/TreeView.tsx     | 16 +++---
 src/client/views/nodes/DocumentView.tsx       | 74 ++++++++++++-------------
 src/client/views/nodes/button/FontIconBox.tsx | 12 ++--
 src/fields/Doc.ts                             |  4 +-
 src/fields/List.ts                            |  8 ++-
 src/fields/Proxy.ts                           | 79 +++++++++++++--------------
 7 files changed, 102 insertions(+), 98 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 4dc1ebd99..052846e71 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -841,15 +841,12 @@ export class MainView extends React.Component {
     }
 
     expandFlyout = action((button: Doc) => {
-        // bcz: What's going on here!?
+        // bcz: What's going on here!? --- may be fixed now, so commenting out ...
         // Chrome(not firefox) seems to have a bug when the flyout expands and there's a zoomed freeform tab.  All of the div below the CollectionFreeFormView's main div
         // generate the wrong value from getClientRectangle() -- specifically they return an 'x' that is the flyout's width greater than it should be.
         // interactively adjusting the flyout fixes the problem.  So does programmatically changing the value after a timeout to something *fractionally* different (ie, 1.5, not 1);)
         this._leftMenuFlyoutWidth = this._leftMenuFlyoutWidth || 250;
-        setTimeout(
-            action(() => (this._leftMenuFlyoutWidth += 0.5)),
-            0
-        );
+        //setTimeout(action(() => (this._leftMenuFlyoutWidth += 0.5)));
 
         this._sidebarContent.proto = button.target as any;
         this.LastButton = button;
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 1e97eee37..ac8562d5a 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -55,7 +55,7 @@ export interface TreeViewProps {
     indentDocument?: (editTitle: boolean) => void;
     outdentDocument?: (editTitle: boolean) => void;
     ScreenToLocalTransform: () => Transform;
-    contextMenuItems: { script: ScriptField; filter: ScriptField; icon: string; label: string }[];
+    contextMenuItems?: { script: ScriptField; filter: ScriptField; icon: string; label: string }[];
     dontRegisterView?: boolean;
     styleProvider?: StyleProviderFunc | undefined;
     treeViewHideHeaderFields: () => boolean;
@@ -302,7 +302,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         const pt = [e.clientX, e.clientY];
         const rect = this._header.current!.getBoundingClientRect();
         const before = pt[1] < rect.top + rect.height / 2;
-        const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length);
+        const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length);
         this._header.current!.className = 'treeView-header';
         if (!this.props.treeView.outlineMode || DragManager.DocDragData?.treeViewDoc === this.props.treeView.rootDoc) {
             if (inside) this._header.current!.className += ' treeView-header-inside';
@@ -362,7 +362,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         if (!this._header.current) return;
         const rect = this._header.current.getBoundingClientRect();
         const before = pt[1] < rect.top + rect.height / 2;
-        const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length);
+        const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
         if (de.complete.linkDragData) {
             const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor();
             const destDoc = this.doc;
@@ -401,7 +401,6 @@ export class TreeView extends React.Component<TreeViewProps> {
     getTransform = () => this.refTransform(this._tref.current);
     embeddedPanelWidth = () => this.props.panelWidth() / (this.props.treeView.props.NativeDimScaling?.() || 1);
     embeddedPanelHeight = () => {
-        console.log(this.props.treeView.rootDoc.title);
         const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
         return Math.min(
             layoutDoc[HeightSym](),
@@ -509,7 +508,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     @computed get renderContent() {
         TraceMobx();
         const expandKey = this.treeViewExpandedView;
-        const sortings = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; label: string } };
+        const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; label: string } }) ?? {};
         if (['links', 'annotations', 'aliases', this.fieldKey].includes(expandKey)) {
             const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None);
             const sortKeys = Object.keys(sortings);
@@ -729,7 +728,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         const openAlias = { script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, icon: 'copy', label: 'Open Alias' };
         const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Focus or Open' };
         return [
-            ...this.props.contextMenuItems.filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)),
+            ...(this.props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)),
             ...(this.doc.isFolder
                 ? folderOp
                 : Doc.IsSystem(this.doc)
@@ -831,7 +830,6 @@ export class TreeView extends React.Component<TreeViewProps> {
      */
     @computed
     get renderTitle() {
-        //
         TraceMobx();
         const view = this._editTitle ? (
             <EditableView
@@ -868,7 +866,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     }
                 })}
                 Document={this.doc}
-                fitWidth={(doc: Doc) => true}
+                fitWidth={returnTrue}
                 DataDoc={undefined}
                 scriptContext={this}
                 hideDecorationTitle={this.props.treeView.outlineMode}
@@ -1035,7 +1033,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         const pt = [de.clientX, de.clientY];
         const rect = this._header.current!.getBoundingClientRect();
         const before = pt[1] < rect.top + rect.height / 2;
-        const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length);
+        const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
 
         const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, false));
     };
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5b26469ed..b5dde211b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,7 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
-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 { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
 import { Document } from '../../../fields/documentSchemas';
@@ -53,7 +53,6 @@ import { RadialMenu } from './RadialMenu';
 import { ScriptingBox } from './ScriptingBox';
 import { PresBox } from './trails/PresBox';
 import React = require('react');
-import { CollectionTreeView } from '../collections/CollectionTreeView';
 const { Howl } = require('howler');
 
 interface Window {
@@ -1056,24 +1055,36 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             : false;
     };
     linkButtonInverseScaling = () => (this.props.NativeDimScaling?.() || 1) * this.props.DocumentView().screenToLocalTransform().Scale;
-    @computed get contents() {
-        TraceMobx();
+    get audioAnnoState() {
+        return this.dataDoc.audioAnnoState ?? 'stopped';
+    }
+    @computed get audioAnnoView() {
         const audioAnnosCount = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null)?.length;
         const audioTextAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations-text'], listSpec('string'), null);
-        const audioView =
-            (!this.props.isSelected() && !this._isHovering && this.dataDoc.audioAnnoState !== 2) || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (!audioAnnosCount && !this.dataDoc.audioAnnoState) ? null : (
-                <Tooltip title={<div>{audioTextAnnos?.lastElement()}</div>}>
-                    <div className="documentView-audioBackground" onPointerDown={this.playAnnotation}>
-                        <FontAwesomeIcon
-                            className="documentView-audioFont"
-                            style={{ color: [audioAnnosCount ? 'blue' : 'gray', 'green', 'red'][NumCast(this.dataDoc.audioAnnoState)] }}
-                            icon={!audioAnnosCount ? 'microphone' : 'file-audio'}
-                            size="sm"
-                        />
-                    </div>
-                </Tooltip>
-            );
-
+        const audioIconColors = new Map<string, string>([
+            ['recording', 'red'],
+            ['playing', 'green'],
+            ['stopped', audioAnnosCount ? 'blue' : 'gray'],
+        ]);
+        return this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (!this.props.isSelected() && !this._isHovering && this.audioAnnoState !== 'recording') || (!audioAnnosCount && this.audioAnnoState === 'stopped') ? null : (
+            <Tooltip title={<div>{audioTextAnnos?.lastElement()}</div>}>
+                <div className="documentView-audioBackground" onPointerDown={this.playAnnotation}>
+                    <FontAwesomeIcon className="documentView-audioFont" style={{ color: audioIconColors.get(StrCast(this.audioAnnoState)) }} icon={!audioAnnosCount ? 'microphone' : 'file-audio'} size="sm" />
+                </div>
+            </Tooltip>
+        );
+    }
+    @computed get linkCountView() {
+        return this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (!this.props.isSelected() && !this._isHovering) || this.hideLinkButton ? null : (
+            <DocumentLinksButton
+                View={this.props.DocumentView()}
+                scaling={this.linkButtonInverseScaling}
+                Offset={[this.topMost ? 0 : !this.props.isSelected() ? -15 : -36, undefined, undefined, this.topMost ? 10 : !this.props.isSelected() ? -15 : -32]}
+            />
+        );
+    }
+    @computed get contents() {
+        TraceMobx();
         return (
             <div
                 className="documentView-contentsView"
@@ -1095,10 +1106,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         width={this.props.PanelWidth()}
                         height={this.props.PanelHeight()}
                         onError={(e: any) => {
-                            setTimeout(
-                                action(() => (this._retryThumb = 0)),
-                                0
-                            );
+                            setTimeout(action(() => (this._retryThumb = 0)));
                             setTimeout(
                                 action(() => (this._retryThumb = 1)),
                                 150
@@ -1124,14 +1132,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     layoutKey={this.finalLayoutKey}
                 />
                 {this.layoutDoc.hideAllLinks ? null : this.allLinkEndpoints}
-                {(!this.props.isSelected() && !this._isHovering) || this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? null : (
-                    <DocumentLinksButton
-                        View={this.props.DocumentView()}
-                        scaling={this.linkButtonInverseScaling}
-                        Offset={[this.topMost ? 0 : !this.props.isSelected() ? -15 : -36, undefined, undefined, this.topMost ? 10 : !this.props.isSelected() ? -15 : -32]}
-                    />
-                )}
-                {audioView}
+                {this.linkCountView}
+                {this.audioAnnoView}
             </div>
         );
     }
@@ -1210,7 +1212,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const self = this;
         const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null);
         const anno = audioAnnos.lastElement();
-        if (anno instanceof AudioField && this.dataDoc.audioAnnoState === 0) {
+        if (anno instanceof AudioField && this.audioAnnoState === 'stopped') {
             new Howl({
                 src: [anno.url.href],
                 format: ['mp3'],
@@ -1218,12 +1220,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 loop: false,
                 volume: 0.5,
                 onend: function () {
-                    runInAction(() => {
-                        self.dataDoc.audioAnnoState = 0;
-                    });
+                    runInAction(() => (self.dataDoc.audioAnnoState = 'stopped'));
                 },
             });
-            this.dataDoc.audioAnnoState = 1;
+            this.dataDoc.audioAnnoState = 'playing';
         }
     };
 
@@ -1262,12 +1262,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         }
                     }
                 };
-                runInAction(() => (dataDoc.audioAnnoState = 2));
+                runInAction(() => (dataDoc.audioAnnoState = 'recording'));
                 recorder.start();
                 setTimeout(() => {
                     recorder.stop();
                     DictationManager.Controls.stop(false);
-                    runInAction(() => (dataDoc.audioAnnoState = 0));
+                    runInAction(() => (dataDoc.audioAnnoState = 'stopped'));
                     gumStream.getAudioTracks()[0].stop();
                 }, 5000);
             });
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 883c4460b..2b83e9da8 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -198,7 +198,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                             e.preventDefault();
                         }}
                         onClick={action(() => (this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen))}>
-                        <input style={{ width: 30 }} className="button-input" type="number" value={checkResult} onChange={action(e => setValue(Number(e.target.value)))} />
+                        <input style={{ width: 30 }} className="button-input" type="number" value={checkResult} onChange={undoBatch(action(e => setValue(Number(e.target.value))))} />
                     </div>
                     <div className={`button`} onClick={action(e => setValue(Number(checkResult) + 1))}>
                         <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'plus'} />
@@ -221,7 +221,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 </div>
             );
         } else {
-            return <div></div>;
+            return <div />;
         }
     }
 
@@ -303,7 +303,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                         fontFamily: script.script.originalScript.startsWith('setFont') ? value : undefined,
                         backgroundColor: value === text ? Colors.LIGHT_BLUE : undefined,
                     }}
-                    onClick={() => script.script.run({ value }).result}>
+                    onClick={undoBatch(() => script.script.run({ value }))}>
                     {value[0].toUpperCase() + value.slice(1)}
                 </div>
             ));
@@ -352,12 +352,14 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
     }
 
     colorPicker = (curColor: string) => {
-        const change = (value: ColorState) => {
+        const change = (value: ColorState, ev: MouseEvent) => {
+            ev.preventDefault();
+            ev.stopPropagation();
             const s = this.colorScript;
             s && undoBatch(() => s.script.run({ value: Utils.colorString(value), _readOnly_: false }).result)();
         };
         const presets = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent'];
-        return <SketchPicker onChange={change} color={curColor} presetColors={presets} />;
+        return <SketchPicker onChange={change as any /* SketchPicker passes the mouse event to the callback, but the type system doesn't know that */} color={curColor} presetColors={presets} />;
     };
     /**
      * Color button
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index fc43325fe..70cb10970 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -756,12 +756,13 @@ export namespace Doc {
             }
             cloneMap.set(doc[Id], copy);
         }
+        Doc.IsPrototype(copy) && Doc.AddDocToList(Doc.MyFileOrphans, undefined, copy);
         return copy;
     }
     export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false, cloneMap: Map<string, Doc> = new Map()) {
         const linkMap = new Map<Doc, Doc>();
         const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = [];
-        const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf'], dontCreate, asBranch);
+        const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf', 'context'], dontCreate, asBranch);
         Array.from(linkMap.entries()).map((links: Doc[]) => LinkManager.Instance.addLink(links[1], true));
         rtfMap.map(({ copy, key, field }) => {
             const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
@@ -974,6 +975,7 @@ export namespace Doc {
         if (retitle) {
             copy.title = incrementTitleCopy(StrCast(copy.title));
         }
+        Doc.IsPrototype(copy) && Doc.AddDocToList(Doc.MyFileOrphans, undefined, copy);
         return copy;
     }
 
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 5cc4ca543..edaa16003 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -278,7 +278,13 @@ class ListImpl<T extends Field> extends ObjectField {
             const batchPromise = DocServer.GetRefFields(promised.map(p => p.promisedFieldId));
             // as soon as we get the fields from the server, set all the list values in one
             // action to generate one React dom update.
-            batchPromise.then(pfields => promised.forEach(p => p.field.setValue(pfields[p.promisedFieldId])));
+            batchPromise.then(
+                action(pfields => {
+                    for (let i = 0; i < promised.length; i++) {
+                        promised[i].field.setValue(pfields[promised[i].promisedFieldId]);
+                    }
+                })
+            );
             // we also have to mark all lists items with this promise so that any calls to them
             // will await the batch request and return the requested field value.
             // This assumes the handler for 'promise' in the call above being invoked before the
diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts
index 2c5f38818..e924ef7a3 100644
--- a/src/fields/Proxy.ts
+++ b/src/fields/Proxy.ts
@@ -1,28 +1,28 @@
-import { Deserializable } from "../client/util/SerializationHelper";
-import { FieldWaiting } from "./Doc";
-import { primitive, serializable } from "serializr";
-import { observable, action, runInAction } from "mobx";
-import { DocServer } from "../client/DocServer";
-import { RefField } from "./RefField";
-import { ObjectField } from "./ObjectField";
-import { Id, Copy, ToScriptString, ToString } from "./FieldSymbols";
-import { scriptingGlobal } from "../client/util/ScriptingGlobals";
-import { Plugins } from "./util";
+import { Deserializable } from '../client/util/SerializationHelper';
+import { FieldWaiting } from './Doc';
+import { primitive, serializable } from 'serializr';
+import { observable, action, runInAction } from 'mobx';
+import { DocServer } from '../client/DocServer';
+import { RefField } from './RefField';
+import { ObjectField } from './ObjectField';
+import { Id, Copy, ToScriptString, ToString } from './FieldSymbols';
+import { scriptingGlobal } from '../client/util/ScriptingGlobals';
+import { Plugins } from './util';
 
 function deserializeProxy(field: any) {
     if (!field.cache) {
         field.cache = DocServer.GetCachedRefField(field.fieldId) as any;
     }
 }
-@Deserializable("proxy", deserializeProxy)
+@Deserializable('proxy', deserializeProxy)
 export class ProxyField<T extends RefField> extends ObjectField {
     constructor();
     constructor(value: T);
     constructor(fieldId: string);
     constructor(value?: T | string) {
         super();
-        if (typeof value === "string") {
-            this.cache = DocServer.GetCachedRefField(value) as any;
+        if (typeof value === 'string') {
+            //this.cache = DocServer.GetCachedRefField(value) as any;
             this.fieldId = value;
         } else if (value) {
             this.cache = value;
@@ -36,16 +36,16 @@ export class ProxyField<T extends RefField> extends ObjectField {
     }
 
     [ToScriptString]() {
-        return "invalid";
+        return 'invalid';
     }
     [ToString]() {
-        return "ProxyField";
+        return 'ProxyField';
     }
 
     @serializable(primitive())
-    readonly fieldId: string = "";
+    readonly fieldId: string = '';
 
-    // This getter/setter and nested object thing is 
+    // This getter/setter and nested object thing is
     // because mobx doesn't play well with observable proxies
     @observable.ref
     private _cache: { readonly field: T | undefined } = { field: undefined };
@@ -59,29 +59,29 @@ export class ProxyField<T extends RefField> extends ObjectField {
     private failed = false;
     private promise?: Promise<any>;
 
+    @action
     value(): T | undefined | FieldWaiting<T> {
-        if (this.cache) {
-            return this.cache;
-        }
-        if (this.failed) {
-            return undefined;
-        }
-        if (!this.promise) {
-            const cached = DocServer.GetCachedRefField(this.fieldId);
-            if (cached !== undefined) {
-                runInAction(() => this.cache = cached as any);
-                return cached as any;
-            }
-            this.promise = DocServer.GetRefField(this.fieldId).then(action((field: any) => {
-                this.promise = undefined;
-                this.cache = field;
-                if (field === undefined) this.failed = true;
-                return field;
-            }));
+        if (this.cache) return this.cache;
+        if (this.failed) return undefined;
+
+        const cached = DocServer.GetCachedRefField(this.fieldId) as T;
+        if (cached !== undefined) {
+            this.cache = cached;
+        } else if (!this.promise) {
+            this.promise = DocServer.GetRefField(this.fieldId).then(
+                action((field: any) => {
+                    this.promise = undefined;
+                    this.cache = field;
+                    this.failed = field === undefined;
+                    return field;
+                })
+            ) as FieldWaiting<T>;
         }
-        return DocServer.GetCachedRefField(this.fieldId) ?? (this.promise as any);
+        return cached ?? this.promise;
+    }
+    promisedValue(): string {
+        return !this.cache && !this.failed && !this.promise && !DocServer.GetCachedRefField(this.fieldId) ? this.fieldId : '';
     }
-    promisedValue(): string { return !this.cache && !this.failed && !this.promise ? this.fieldId : ""; }
     setPromise(promise: any) {
         this.promise = promise;
     }
@@ -127,6 +127,5 @@ function prefetchValue(proxy: PrefetchProxy<RefField>) {
 }
 
 @scriptingGlobal
-@Deserializable("prefetch_proxy", prefetchValue)
-export class PrefetchProxy<T extends RefField> extends ProxyField<T> {
-}
+@Deserializable('prefetch_proxy', prefetchValue)
+export class PrefetchProxy<T extends RefField> extends ProxyField<T> {}
-- 
cgit v1.2.3-70-g09d2

