From 4c1933b67dad98c7e5e7f77b0bebc8ae2a612bca Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 12 Sep 2024 10:24:55 -0400
Subject: added menu option

---
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx     | 8 ++++++++
 1 file changed, 8 insertions(+)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index dbf781e63..38c85edce 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1949,6 +1949,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         !options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
         const mores = ContextMenu.Instance.findByDescription('More...');
         const moreItems = mores?.subitems ?? [];
+        moreItems.push({
+            description: 'recognize all ink',
+            event: () => {
+                this.unprocessedDocs.push(...this.childDocs.filter(doc => doc.type === DocumentType.INK));
+                CollectionFreeFormView.collectionsWithUnprocessedInk.add(this);
+            },
+            icon: 'pen',
+        });
         !mores && ContextMenu.Instance.addItem({ description: 'More...', subitems: moreItems, icon: 'eye' });
     };
 
-- 
cgit v1.2.3-70-g09d2


From 9808c01be554ab210083af9c1ef7fd45fbd84c19 Mon Sep 17 00:00:00 2001
From: Zachary Zhang <zacharyzhang7@gmail.com>
Date: Wed, 25 Sep 2024 21:26:31 -0400
Subject: fix scribble gesture to use bezier curve intersections

---
 src/client/views/GestureOverlay.tsx                | 196 ++++++++++-----------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   1 -
 2 files changed, 91 insertions(+), 106 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 935eeb251..7e396d475 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -1,4 +1,5 @@
 import * as fitCurve from 'fit-curve';
+import { Bezier } from 'bezier-js';
 import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -95,7 +96,6 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
     componentDidMount() {
         GestureOverlay.Instance = this;
     }
-
     @action
     onPointerDown = (e: React.PointerEvent) => {
         if (!(e.target as HTMLElement)?.className?.toString().startsWith('lm_')) {
@@ -139,52 +139,52 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
      * to be deleted and then it will delete them.
      * @returns
      */
-    isScribble() {
+    isScribble(inkData: InkData) {
         const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView);
-        const cuspArray = this.getCusps();
-        const cuspBooleanArray: boolean[] = [];
+        console.log(inkData.map(ink => ({ x: ink.X, y: ink.Y })));
+        let intersectArray: boolean[] = [];
+        const cuspArray = this.getCusps(inkData);
+        console.log(cuspArray.length);
+        for (let i = 0; i < cuspArray.length; i++) {
+            intersectArray[i] = false;
+        }
         const docsToDelete: Doc[] = [];
-        const childDocs = (ffView?.ComponentView as CollectionFreeFormView).childDocs;
+        const childDocs = (ffView?.ComponentView as CollectionFreeFormView).childDocs.slice(0, -1);
         childDocs.filter(doc => doc.type === 'ink').map(doc => DocumentView.getDocumentView(doc, DocumentView.getDocumentView(doc)));
         if ((ffView?.ComponentView as CollectionFreeFormView).childDocs) {
             //how many cusps the scribble hsa
-            if (cuspArray.length > 4) {
-                for (let i = 0; i < cuspArray.length - 2; i++) {
-                    let hasDocInTriangle = false;
-                    for (const doc of childDocs) {
-                        const point1 = cuspArray[i];
-                        const point2 = cuspArray[i + 1];
-                        const point3 = cuspArray[i + 2];
-                        const triangleObject = { p1: { X: point1.X, Y: point1.Y }, p2: { X: point2.X, Y: point2.Y }, p3: { X: point3.X, Y: point3.Y } };
-                        const otherInk = DocumentView.getDocumentView(doc)?.ComponentView as InkingStroke;
-                        if (otherInk instanceof InkingStroke) {
-                            const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
-                            const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point));
-                            if (doc.title === 'line') {
-                                if (this.doesLineIntersectTriangle(otherScreenPts, triangleObject)) {
-                                    docsToDelete.push(doc);
-                                    hasDocInTriangle = true;
-                                    cuspBooleanArray.push(true);
-                                }
+            for (const doc of childDocs) {
+                const otherInk = DocumentView.getDocumentView(doc)?.ComponentView as InkingStroke;
+                if (otherInk instanceof InkingStroke) {
+                    const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
+                    const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point));
+                    if (this.isRectangleOverlap(this.getExtremeCoordinates(otherScreenPts), this.getExtremeCoordinates(inkData))) {
+                        console.log('somethings in');
+                        const intersects = this.doInksIntersect(inkData, otherScreenPts);
+                        console.log(intersects);
+                        intersects.forEach(intersect => {
+                            let percentage = '';
+                            if (intersect.includes('/')) {
+                                const leftOfSlash = intersect.split('/')[0];
+                                percentage = leftOfSlash;
                             } else {
-                                if (this.isAnyPointInTriangle(triangleObject, otherScreenPts)) {
-                                    docsToDelete.push(doc);
-                                    hasDocInTriangle = true;
-                                    cuspBooleanArray.push(true);
-                                }
+                                percentage = intersect;
                             }
-                        }
+                            intersectArray[Math.floor((percentage as unknown as number) * cuspArray.length)] = true;
+                            docsToDelete.push(doc);
+                        });
                     }
-                    cuspBooleanArray.push(hasDocInTriangle);
-                }
-                if (this.determineIfScribble(cuspBooleanArray)) {
-                    docsToDelete.forEach(doc => {
-                        ffView?.ComponentView?.removeDocument?.(doc);
-                    });
-                    this._points = [];
-                    return true;
                 }
             }
+            console.log(intersectArray);
+            if (intersectArray.length > 3 && this.determineIfScribble(intersectArray)) {
+                console.log('is a scribble');
+                docsToDelete.forEach(doc => {
+                    ffView?.ComponentView?.removeDocument?.(doc);
+                });
+                this._points = [];
+                return true;
+            }
         }
         return false;
     }
@@ -198,7 +198,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
         if (!cuspBooleanArray) {
             return false;
         }
-        const quarterArrayLength = Math.ceil((cuspBooleanArray.length - 2) * 0.25);
+        const quarterArrayLength = Math.ceil((cuspBooleanArray.length - 2) * 0.3);
         let hasObjectInFirstAndLast25 = true;
         for (let i = 0; i < quarterArrayLength; i++) {
             if (cuspBooleanArray[i] == false || cuspBooleanArray[cuspBooleanArray.length - 1 - i] == false) {
@@ -207,7 +207,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
         }
         const trueCount = cuspBooleanArray.filter(value => value).length;
         const percentageTrues = trueCount / cuspBooleanArray.length;
-        return percentageTrues > 0.65 || hasObjectInFirstAndLast25;
+        return percentageTrues > 0.5 || hasObjectInFirstAndLast25;
     }
     /**
      * determines if two rectangles are overlapping each other
@@ -221,38 +221,6 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
 
         return !noOverlap;
     }
-    /**
-     * determines if there is a point in a triangle used to determine what triangles in the scribble have an object
-     * @param pt the point in question
-     * @param triangle the triangle with 3 points
-     * @returns true or false if point is in triangle
-     */
-    isPointInTriangle(pt: { X: number; Y: number }, triangle: { p1: { X: number; Y: number }; p2: { X: number; Y: number }; p3: { X: number; Y: number } }): boolean {
-        const area = (v1: { X: number; Y: number }, v2: { X: number; Y: number }, v3: { X: number; Y: number }) => Math.abs((v1.X * (v2.Y - v3.Y) + v2.X * (v3.Y - v1.Y) + v3.X * (v1.Y - v2.Y)) / 2.0);
-
-        const A = area(triangle.p1, triangle.p2, triangle.p3);
-
-        const A1 = area(pt, triangle.p2, triangle.p3);
-        const A2 = area(triangle.p1, pt, triangle.p3);
-        const A3 = area(triangle.p1, triangle.p2, pt);
-
-        return A === A1 + A2 + A3;
-    }
-    /**
-     * determines if any points in an array are in a triangle
-     * @param triangle the triangle with 3 points
-     * @param points the point in question
-     * @returns true or false if point is in triangle
-     */
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    isAnyPointInTriangle(triangle: any, points: any[]): boolean {
-        for (const point of points) {
-            if (this.isPointInTriangle(point, triangle)) {
-                return true;
-            }
-        }
-        return false;
-    }
     /**
      * determines if a line intersects a triangle. used for scribble gesture since the line doesnt have a lot
      * of points across is so isPointInTriangle will not work for it.
@@ -261,20 +229,25 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
      * @returns true or false if it intersects
      */
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    doesLineIntersectTriangle(line: any, triangle: any): boolean {
-        const edges = [
-            { start: triangle.p1, end: triangle.p2 },
-            { start: triangle.p2, end: triangle.p3 },
-            { start: triangle.p3, end: triangle.p1 },
-        ];
-
-        for (const edge of edges) {
-            if (this.doLinesIntersect(line, edge)) {
-                return true;
+    doInksIntersect(scribble: InkData, inkStroke: InkData): string[] {
+        let intersectArray: string[] = [];
+        const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView);
+        if (ffView && ffView.ComponentView instanceof CollectionFreeFormView) {
+            for (let i = 0; i < scribble.length - 3; i += 4) {
+                // iterate over each segment of bezier curve
+                for (let j = 0; j < inkStroke.length - 3; j += 4) {
+                    const intersectCurve: Bezier = InkField.Segment(scribble, i); // other curve
+                    const eraserCurve: Bezier = InkField.Segment(inkStroke, j); // eraser curve
+                    if (ffView && ffView.ComponentView instanceof CollectionFreeFormView) {
+                        const result = (ffView.ComponentView as CollectionFreeFormView).bintersects(intersectCurve, eraserCurve)[0];
+                        if (result !== undefined) {
+                            intersectArray.push(result.toString());
+                        }
+                    }
+                }
             }
         }
-
-        return false;
+        return intersectArray;
     }
     /**
      * used in doesLineIntersectTriangle, splits up the triangle into 3 lines and runs this method
@@ -284,20 +257,21 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
      * @returns
      */
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    doLinesIntersect(line1: any, line2: any): boolean {
-        const A = line1[0];
-        const B = line1[line1.length - 1];
-        const { start: C, end: D } = line2;
-        const denominator = (B.X - A.X) * (D.Y - C.Y) - (B.Y - A.Y) * (D.X - C.X);
-        if (denominator === 0) return false;
-
-        const numerator1 = (A.Y - C.Y) * (D.X - C.X) - (A.X - C.X) * (D.Y - C.Y);
-        const numerator2 = (A.Y - C.Y) * (B.X - A.X) - (A.X - C.X) * (B.Y - A.Y);
-
-        const r = numerator1 / denominator;
-        const s = numerator2 / denominator;
-
-        return r >= 0 && r <= 1 && s >= 0 && s <= 1;
+    doLinesIntersect(points: any, line: any): boolean {
+        const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView);
+        if (ffView && ffView.ComponentView instanceof CollectionFreeFormView) {
+            for (let i = 0; i < points.length - 3; i += 4) {
+                // iterate over each segment of bezier curve
+                for (let j = 0; j < line.length - 3; j += 4) {
+                    const intersectCurve: Bezier = InkField.Segment(points, i); // other curve
+                    const eraserCurve: Bezier = InkField.Segment(line, j); // eraser curve
+                    if (ffView && ffView.ComponentView instanceof CollectionFreeFormView) {
+                        //(ffView.ComponentView as CollectionFreeFormView).bintersects();
+                    }
+                }
+            }
+        }
+        return false;
     }
 
     dryInk = () => {
@@ -343,18 +317,31 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
                     case Gestures.Rectangle:
                     case Gestures.Circle:
                         this.makeBezierPolygon(Name, true);
+                        setTimeout(() => {
+                            const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView);
+                            const scribbleInk = (ffView?.ComponentView as CollectionFreeFormView).childDocs[(ffView?.ComponentView as CollectionFreeFormView).childDocs.length - 1];
+                            //this.isScribble((DocumentView.getDocumentView(scribbleInk)?.ComponentView as InkingStroke).inkScaledData().inkData);
+                        }, 1);
                         return this.dispatchGesture(name);
                     case Gestures.RightAngle:
                         return this.convertToText().length > 0;
                     default:
-                    case Gestures.Stroke:
-                        return this.isScribble();
                 }
             })(Score < 0.7 ? Gestures.Stroke : (Name as Gestures));
             // if no gesture (or if the gesture was unsuccessful), "dry" the stroke into an ink document
-            if (!actionPerformed) this.dryInk();
+            if (!actionPerformed) {
+                this.dryInk();
+                setTimeout(() => {
+                    const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView);
+                    const scribbleInk = (ffView?.ComponentView as CollectionFreeFormView).childDocs[(ffView?.ComponentView as CollectionFreeFormView).childDocs.length - 1];
+                    if (this.isScribble((DocumentView.getDocumentView(scribbleInk)?.ComponentView as InkingStroke).inkScaledData().inkData)) {
+                        ffView?.ComponentView?.removeDocument?.(scribbleInk);
+                    }
+                }, 1);
+            }
         }
         this._points.length = 0;
+        //console.log((DocumentView.getDocumentView(scribbleInk)?.ComponentView as InkingStroke).inkScaledData());
     };
     /**
      * used in the rightAngle gesture to convert handwriting into text. will only work on collections
@@ -374,7 +361,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
                     const bounds = DocumentView.getDocumentView(doc)?.getBounds;
                     if (bounds) {
                         const rect1 = { minX: bounds.left, maxX: bounds.right, minY: bounds.top, maxY: bounds.bottom };
-                        if (this.isRectangleOverlap(rect1, this.getExtremeCoordinates())) {
+                        if (this.isRectangleOverlap(rect1, this.getExtremeCoordinates(this._points))) {
                             if (doc.x < minX) {
                                 minX = doc.x;
                             }
@@ -405,8 +392,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
      * used to determine how many cusps and where the cusps are in order
      * @returns will return an array containing the coordinates of the sharp cusps
      */
-    getCusps() {
-        const points = this._points.map(p => ({ X: p.X, Y: p.Y }));
+    getCusps(points: InkData) {
         const arrayOfPoints: { X: number; Y: number }[] = [];
         arrayOfPoints.push(points[0]);
         for (let i = 0; i < points.length - 2; i++) {
@@ -424,8 +410,8 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
      * will look through an array of point data and return the coordinates of the smallest box that can fit all the points
      * @returns the minX,maxX,minY,maxY of the box
      */
-    getExtremeCoordinates() {
-        const coordinates = this._points;
+    getExtremeCoordinates(points: { X: number; Y: number }[]) {
+        const coordinates = points;
         if (coordinates.length === 0) {
             throw new Error('Coordinates array is empty');
         }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 38c85edce..6ae55add3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -890,7 +890,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     strokeToTVals.set(inkView, [Math.floor(inkData.length / 4) + 1]);
                 }
             }
-
             for (let i = 0; i < inkData.length - 3; i += 4) {
                 // iterate over each segment of bezier curve
                 for (let j = 0; j < eraserInkData.length - 3; j += 4) {
-- 
cgit v1.2.3-70-g09d2


From 42419cd85b7dc2aec0695f2b29b2f707ae7e36e2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 27 Sep 2024 14:19:40 -0400
Subject: fixed linting.  cleaned up scribble erase code and fixed several
 issues with determining if cusp intersections amounted to a scribble.  also
 fixed recognition of lines to not recognize scribbles.

---
 eslint.config.mjs                                  |   2 +-
 package-lock.json                                  |  14 ++
 src/.DS_Store                                      | Bin 14340 -> 10244 bytes
 src/Utils.ts                                       |   3 +-
 src/client/views/GestureOverlay.tsx                | 276 +++++++--------------
 src/client/views/InkingStroke.tsx                  |   6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  24 +-
 src/fields/InkField.ts                             |  20 ++
 8 files changed, 137 insertions(+), 208 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/eslint.config.mjs b/eslint.config.mjs
index aebdc20d0..f7063caa5 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -48,7 +48,7 @@ export default [
             'no-return-assign': 'error',
             'no-await-in-loop': 'error',
             'no-loop-func': 'error',
-            '@typescript-eslint/no-cond-assign': 'error',
+            'no-cond-assign': 'error',
             'no-use-before-define': 'error',
             '@typescript-eslint/no-explicit-any': 'error',
             'no-restricted-globals': ['error', 'event'],
diff --git a/package-lock.json b/package-lock.json
index fac1cc20b..727b22f34 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -130,6 +130,7 @@
                 "https": "^1.0.0",
                 "https-browserify": "^1.0.0",
                 "i": "^0.3.7",
+                "iink-ts": "^1.0.5",
                 "image-data-uri": "^2.0.1",
                 "image-size": "^1.0.2",
                 "image-size-stream": "^1.1.0",
@@ -20506,6 +20507,14 @@
             "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
             "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA=="
         },
+        "node_modules/iink-ts": {
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/iink-ts/-/iink-ts-1.0.5.tgz",
+            "integrity": "sha512-LAWWPvgcsLtouI9ExVijZbPGIWMfJtVCcuznbIDyboaPb+cHYsftcuJdjDU7TQwIpGP4hmcjNQA57XPCw4MK2A==",
+            "dependencies": {
+                "json-css": "^1.5.6"
+            }
+        },
         "node_modules/image-data-uri": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/image-data-uri/-/image-data-uri-2.0.1.tgz",
@@ -21989,6 +21998,11 @@
             "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
             "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
         },
+        "node_modules/json-css": {
+            "version": "1.5.6",
+            "resolved": "https://registry.npmjs.org/json-css/-/json-css-1.5.6.tgz",
+            "integrity": "sha512-B/0T0OxZH9tSb93tXV6VOYtXqrPz/Vgz2QrCT/4NXen8HGElYkYr9V+8IrSVTMj/ftxa8cG1kcu7f3iAMlaFlQ=="
+        },
         "node_modules/json-parse-even-better-errors": {
             "version": "2.3.1",
             "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
diff --git a/src/.DS_Store b/src/.DS_Store
index 19eea5fce..b91453587 100644
Binary files a/src/.DS_Store and b/src/.DS_Store differ
diff --git a/src/Utils.ts b/src/Utils.ts
index 0590c6930..724725c23 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/no-namespace */
 import * as uuid from 'uuid';
 
 export function clamp(n: number, lower: number, upper: number) {
@@ -205,7 +204,6 @@ export function intersectRect(r1: { left: number; top: number; width: number; he
 }
 
 export function stringHash(s?: string) {
-    // eslint-disable-next-line no-bitwise
     return !s ? undefined : Math.abs(s.split('').reduce((a, b) => (n => n & n)((a << 5) - a + b.charCodeAt(0)), 0));
 }
 
@@ -254,6 +252,7 @@ export namespace JSONUtils {
         try {
             results = JSON.parse(source);
         } catch (e) {
+            console.log('JSONparse error: ', e);
             results = source;
         }
         return results;
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 1cd6de55c..e9e12fc3b 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -1,10 +1,9 @@
 import * as fitCurve from 'fit-curve';
-import { Bezier } from 'bezier-js';
 import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../ClientUtils';
-import { emptyFunction } from '../../Utils';
+import { emptyFunction, intersectRect } from '../../Utils';
 import { Doc, Opt, returnEmptyDoclist } from '../../fields/Doc';
 import { InkData, InkField, InkTool } from '../../fields/InkField';
 import { NumCast } from '../../fields/Types';
@@ -16,6 +15,7 @@ import { Docs } from '../documents/Documents';
 import { InteractionUtils } from '../util/InteractionUtils';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
 import { Transform } from '../util/Transform';
+import { undoable } from '../util/UndoManager';
 import './GestureOverlay.scss';
 import { InkingStroke } from './InkingStroke';
 import { ObservableReactComponent } from './ObservableReactComponent';
@@ -37,7 +37,6 @@ import {
     SetActiveInkColor,
     SetActiveInkWidth,
 } from './nodes/DocumentView';
-import { docs } from 'googleapis/build/src/apis/docs';
 export enum ToolglassTools {
     InkToText = 'inktotext',
     IgnoreGesture = 'ignoregesture',
@@ -53,7 +52,9 @@ interface GestureOverlayProps {
  * drew or perform the gesture's action
  */
 export class GestureOverlay extends ObservableReactComponent<React.PropsWithChildren<GestureOverlayProps>> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: GestureOverlay;
+    // eslint-disable-next-line no-use-before-define
     static Instances: GestureOverlay[] = [];
 
     @observable public InkShape: Opt<Gestures> = undefined;
@@ -79,10 +80,6 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
     }
 
     private _overlayRef = React.createRef<HTMLDivElement>();
-    private _d1: Doc | undefined;
-    private _inkToTextDoc: Doc | undefined;
-    private thumbIdentifier?: number;
-    private pointerIdentifier?: number;
 
     constructor(props: GestureOverlayProps) {
         super(props);
@@ -136,119 +133,67 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
         }
     }
     /**
-     * this method returns if what the user drew is a scribble. if it is, it will determine what documents need
-     * to be deleted and then it will delete them.
+     * If what the user drew is a scribble, this returns the documents that were scribbled over
      * I changed it so it doesnt use triangles. It will modify an intersect array, with its length being
      * how many sharp cusps there are. The first index will have a boolean that is based on if there is an
      * intersection in the first 1/length percent of the stroke. The second index will be if there is an intersection
-     * in the 2nd 1/length percent of the stroke. This array will be used in determineInScribble().
-     * @returns
+     * in the 2nd 1/length percent of the stroke. This array will be used in determineIfScribble().
+     * @param ffview freeform view where scribble is drawn
+     * @param scribbleStroke scribble stroke in screen space coordinats
+     * @returns array of documents scribbled over
      */
-    isScribble(inkData: InkData) {
-        const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView);
-        console.log(inkData.map(ink => ({ x: ink.X, y: ink.Y })));
-        let intersectArray: boolean[] = [];
-        const cuspArray = this.getCusps(inkData);
-        console.log(cuspArray.length);
-        for (let i = 0; i < cuspArray.length; i++) {
-            intersectArray[i] = false;
-        }
-        const docsToDelete: Doc[] = [];
-        let childDocs = (ffView?.ComponentView as CollectionFreeFormView).childDocs.slice(0, -1);
-        childDocs.filter(doc => doc.type === 'ink').map(doc => DocumentView.getDocumentView(doc, DocumentView.getDocumentView(doc)));
-        if ((ffView?.ComponentView as CollectionFreeFormView).childDocs) {
-            for (const doc of childDocs) {
-                const otherInk = DocumentView.getDocumentView(doc)?.ComponentView as InkingStroke;
-                const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
-                const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point));
-                if (this.isRectangleOverlap(this.getExtremeCoordinates(otherScreenPts), this.getExtremeCoordinates(inkData))) {
-                    const intersects = this.doInksIntersect(inkData, otherScreenPts);
-                    intersects.forEach(intersect => {
-                        let percentage = '';
-                        if (intersect.includes('/')) {
-                            const leftOfSlash = intersect.split('/')[0];
-                            percentage = leftOfSlash;
-                        } else {
-                            percentage = intersect;
-                        }
-                        intersectArray[Math.floor((percentage as unknown as number) * cuspArray.length)] = true;
-                        const docsInBoundingBox = this.docsInBoundingBox(doc, childDocs);
-                        childDocs = childDocs.filter(doc => !docsInBoundingBox.includes(doc));
-                        docsToDelete.push(...docsInBoundingBox);
-                        docsToDelete.push(doc);
+    isScribble = (ffView: CollectionFreeFormView, cuspArray: { X: number; Y: number }[], scribbleStroke: { X: number; Y: number }[]) => {
+        const intersectArray = cuspArray.map(() => false);
+        const scribbleBounds = InkField.getBounds(scribbleStroke);
+        const docsToDelete = ffView.childDocs
+            .map(doc => DocumentView.getDocumentView(doc))
+            .filter(dv => dv?.ComponentView instanceof InkingStroke)
+            .map(dv => dv?.ComponentView as InkingStroke)
+            .filter(otherInk => {
+                const otherScreenPts = otherInk.inkScaledData?.().inkData.map(otherInk.ptToScreen);
+                if (intersectRect(InkField.getBounds(otherScreenPts), scribbleBounds)) {
+                    const intersects = this.findInkIntersections(scribbleStroke, otherScreenPts).map(intersect => {
+                        const percentage = intersect.split('/')[0];
+                        intersectArray[Math.floor(Number(percentage) * cuspArray.length)] = true;
                     });
+                    return intersects.length > 0;
                 }
-            }
-            console.log(intersectArray);
-            if (intersectArray.length > 3 && this.determineIfScribble(intersectArray)) {
-                const uniqueArray = Array.from(new Set(docsToDelete));
-                console.log(uniqueArray.length);
-                console.log('is a scribble');
-                docsToDelete.forEach(doc => {
-                    ffView?.ComponentView?.removeDocument?.(doc);
-                });
-                this._points = [];
-                return true;
-            }
-        }
-        return false;
-    }
-    /**
-     * this will return all the docs overlapping with the maindocs bounding box
-     * @param mainDoc the bounding box of this doc will be used
-     * @param childDocs the array of all docs in collection
-     * @returns
-     */
-    docsInBoundingBox(mainDoc: Doc, childDocs: Doc[]): Doc[] {
-        return childDocs.filter(
-            doc =>
-                typeof doc.x === 'number' &&
-                typeof doc.y === 'number' &&
-                typeof doc.width === 'number' &&
-                typeof doc.height === 'number' &&
-                typeof mainDoc.x === 'number' &&
-                typeof mainDoc.y === 'number' &&
-                typeof mainDoc.width === 'number' &&
-                typeof mainDoc.height === 'number' &&
-                doc.x < mainDoc.x + mainDoc.width &&
-                doc.x + doc.width > mainDoc.x &&
-                doc.y < mainDoc.y + mainDoc.height &&
-                doc.y + doc.height > mainDoc.y
-        );
-    }
+            });
+        return !this.determineIfScribble(intersectArray) ? undefined : 
+            [ ...docsToDelete.map(stroke => stroke.Document),
+               // bcz: NOTE: docsInBoundingBox test should be replaced with a docsInConvexHull test
+              ...this.docsInBoundingBox({ topLeft    : ffView.ScreenToContentsXf().transformPoint(scribbleBounds.left, scribbleBounds.top),
+                                          bottomRight: ffView.ScreenToContentsXf().transformPoint(scribbleBounds.right,scribbleBounds.bottom)},
+                                        ffView.childDocs.filter(doc => !docsToDelete.map(s => s.Document).includes(doc)) )]; // prettier-ignore
+    };
     /**
-     * this method determines if what the user drew is a scribble based on certain criteria.
-     * @param cuspBooleanArray will take in an array of booleans tht represent what sections(seperated by a cusp) in the scribble
-     * has an object in it.
-     * @returns
+     * Returns all docs in array that overlap bounds.  Note that the bounds should be given in screen space coordinates.
+     * @param boundingBox screen space bounding box
+     * @param childDocs array of docs to test against bounding box
+     * @returns list of docs that overlap rect
      */
-    determineIfScribble(cuspBooleanArray: boolean[]) {
-        if (!cuspBooleanArray) {
-            return false;
-        }
-        const quarterArrayLength = Math.ceil((cuspBooleanArray.length - 2) * 0.3);
-        let hasObjectInFirstAndLast25 = true;
-        for (let i = 0; i < quarterArrayLength; i++) {
-            if (cuspBooleanArray[i] == false || cuspBooleanArray[cuspBooleanArray.length - 1 - i] == false) {
-                hasObjectInFirstAndLast25 = false;
-            }
-        }
-        const trueCount = cuspBooleanArray.filter(value => value).length;
-        const percentageTrues = trueCount / cuspBooleanArray.length;
-        return percentageTrues >= 0.5 || hasObjectInFirstAndLast25;
-    }
+    docsInBoundingBox = (boundingBox: { topLeft: number[]; bottomRight: number[] }, childDocs: Doc[]): Doc[] => {
+        const rect = { left: boundingBox.topLeft[0], top: boundingBox.topLeft[1], width: boundingBox.bottomRight[0] - boundingBox.topLeft[0], height: boundingBox.bottomRight[1] - boundingBox.topLeft[1] };
+        return childDocs.filter(doc => intersectRect(rect, { left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) }));
+    };
     /**
-     * determines if two rectangles are overlapping each other
-     * @param rect1 the rectangle object has has a minX,maxX,minY, and maxY
-     * @param rect2
+     * Determines if what the array of cusp/intersection data corresponds to a scribble.
+     * true if there are at least 4 cusps and either:
+     *    1) the initial and final quarters of the array contain objects
+     *    2) or half of the cusps contain objects
+     * @param intersectArray array of booleans coresponding to which scribble sections (regions separated by a cusp) contain Docs
      * @returns
      */
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    isRectangleOverlap(rect1: any, rect2: any): boolean {
-        const noOverlap = rect1.maxX < rect2.minX || rect1.minX > rect2.maxX || rect1.maxY < rect2.minY || rect1.minY > rect2.maxY;
-
-        return !noOverlap;
-    }
+    determineIfScribble = (intersectArray: boolean[]) => {
+        const quarterArrayLength = Math.ceil(intersectArray.length / 3.9); // use 3.9 instead of 4 to work better with strokes with only 4 cusps
+        const { start, end } = intersectArray.reduce((res, val, i) => ({   // test for scribbles at start and end of scribble stroke
+                start: res.start || (val && i <= quarterArrayLength), 
+                end:   res.end   || (val && i >= intersectArray.length - quarterArrayLength) 
+            }), { start: false, end: false }); // prettier-ignore
+
+        const percentCuspsWithContent = intersectArray.filter(value => value).length / intersectArray.length;
+        return intersectArray.length > 3 && (percentCuspsWithContent >= 0.5 || (start && end));
+    };
     /**
      * determines if inks intersect
      * @param line is pointData
@@ -260,32 +205,24 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
      * it will return two numbers, one for each curve, seperated by a comma. Sometimes, the percentage it returns is inaccurate,
      * espcially in the beginning and end parts of the stroke. dont know why. hope this makes sense
      */
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    doInksIntersect(scribble: InkData, inkStroke: InkData): string[] {
-        let intersectArray: string[] = [];
-        const extremeScribble = this.getExtremeCoordinates(scribble);
-        const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView);
-        if (ffView && ffView.ComponentView instanceof CollectionFreeFormView) {
-            for (let i = 0; i < scribble.length - 3; i += 4) {
-                // iterate over each segment of bezier curve
-                for (let j = 0; j < inkStroke.length - 3; j += 4) {
-                    const scribbleCurve: Bezier = InkField.Segment(scribble, i);
-                    const strokeCurve: Bezier = InkField.Segment(inkStroke, j);
-                    const points = strokeCurve.points.map(point => ({
-                        X: point.x,
-                        Y: point.y,
-                    }));
-                    if (ffView && ffView.ComponentView instanceof CollectionFreeFormView && this.isRectangleOverlap(extremeScribble, this.getExtremeCoordinates(points))) {
-                        const result = (ffView.ComponentView as CollectionFreeFormView).bintersects(scribbleCurve, strokeCurve)[0];
-                        if (result !== undefined) {
-                            intersectArray.push(result.toString());
-                        }
+    findInkIntersections = (scribble: InkData, inkStroke: InkData): string[] => {
+        const intersectArray: string[] = [];
+        const scribbleBounds = InkField.getBounds(scribble);
+        for (let i = 0; i < scribble.length - 3; i += 4) {  // for each segment of scribble
+            for (let j = 0; j < inkStroke.length - 3; j += 4) { // for each segment of ink stroke
+                const scribbleSeg = InkField.Segment(scribble, i);
+                const strokeSeg = InkField.Segment(inkStroke, j);
+                const strokeBounds = InkField.getBounds(strokeSeg.points.map(pt => ({ X: pt.x, Y: pt.y })));
+                if (intersectRect(scribbleBounds, strokeBounds)) {
+                    const result = InkField.bintersects(scribbleSeg, strokeSeg)[0];
+                    if (result !== undefined) {
+                        intersectArray.push(result.toString());
                     }
                 }
-            }
-        }
+            } // prettier-ignore
+        } // prettier-ignore
         return intersectArray;
-    }
+    };
     dryInk = () => {
         const newPoints = this._points.reduce((p, pts) => {
             p.push([pts.X, pts.Y]);
@@ -309,6 +246,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
     };
     @action
     onPointerUp = () => {
+        const ffView = DocumentView.DownDocView?.ComponentView instanceof CollectionFreeFormView && DocumentView.DownDocView.ComponentView;
         DocumentView.DownDocView = undefined;
         if (this._points.length > 1) {
             const B = this.svgBounds;
@@ -320,31 +258,34 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
                       ? GestureUtils.GestureRecognizer.Recognize([points])
                       : undefined) ?? 
                 new Result(Gestures.Stroke, 1, Date.now); // prettier-ignore
+
+            const cuspArray = this.getCusps(points);
             // if any of the shape is activated in the CollectionFreeFormViewChrome
             // need to decide when to turn gestures back on
             const actionPerformed = ((name: Gestures) => {
                 switch (name) {
                     case Gestures.Line:
+                        if (cuspArray.length > 2) return undefined;
+                    // eslint-disable-next-line no-fallthrough
                     case Gestures.Triangle:
                     case Gestures.Rectangle:
                     case Gestures.Circle:
                         this.makeBezierPolygon(Name, true);
                         return this.dispatchGesture(name);
                     case Gestures.RightAngle:
-                        return this.convertToText().length > 0;
+                        return ffView && this.convertToText(ffView).length > 0;
                     default:
                 }
             })(Score < 0.7 ? Gestures.Stroke : (Name as Gestures));
             // if no gesture (or if the gesture was unsuccessful), "dry" the stroke into an ink document
+
             if (!actionPerformed) {
-                this.dryInk();
-                setTimeout(() => {
-                    const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView);
-                    const scribbleInk = (ffView?.ComponentView as CollectionFreeFormView).childDocs[(ffView?.ComponentView as CollectionFreeFormView).childDocs.length - 1];
-                    if (this.isScribble((DocumentView.getDocumentView(scribbleInk)?.ComponentView as InkingStroke).inkScaledData().inkData)) {
-                        ffView?.ComponentView?.removeDocument?.(scribbleInk);
-                    }
-                }, 1);
+                const scribbledOver = ffView && this.isScribble(ffView, cuspArray, this._points);
+                if (scribbledOver) {
+                    undoable(() => ffView.removeDocument(scribbledOver), 'scribble erase')();
+                } else {
+                    this.dryInk();
+                }
             }
         }
         this._points.length = 0;
@@ -353,21 +294,19 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
      * used in the rightAngle gesture to convert handwriting into text. will only work on collections
      * TODO: make it work on individual ink docs.
      */
-    convertToText() {
-        const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView);
+    convertToText = (ffView: CollectionFreeFormView) => {
         let minX = 999999999;
         let maxX = -999999999;
         let minY = 999999999;
         let maxY = -999999999;
         const textDocs: Doc[] = [];
-        (ffView?.ComponentView as CollectionFreeFormView).childDocs
+        ffView.childDocs
             .filter(doc => doc.type === DocumentType.COL)
             .forEach(doc => {
                 if (typeof doc.width === 'number' && typeof doc.height === 'number' && typeof doc.x === 'number' && typeof doc.y === 'number') {
                     const bounds = DocumentView.getDocumentView(doc)?.getBounds;
                     if (bounds) {
-                        const rect1 = { minX: bounds.left, maxX: bounds.right, minY: bounds.top, maxY: bounds.bottom };
-                        if (this.isRectangleOverlap(rect1, this.getExtremeCoordinates(this._points))) {
+                        if (intersectRect({ ...bounds, width: bounds.right - bounds.left, height: bounds.bottom - bounds.top }, InkField.getBounds(this._points))) {
                             if (doc.x < minX) {
                                 minX = doc.x;
                             }
@@ -383,9 +322,9 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
                             const newDoc = Docs.Create.TextDocument(doc.transcription as string, { title: '', x: doc.x as number, y: minY });
                             newDoc.height = doc.height;
                             newDoc.width = doc.width;
-                            if (ffView?.ComponentView?.addDocument && ffView?.ComponentView?.removeDocument) {
-                                ffView.ComponentView.addDocument(newDoc);
-                                ffView.ComponentView.removeDocument(doc);
+                            if (ffView.addDocument && ffView.removeDocument) {
+                                ffView.addDocument(newDoc);
+                                ffView.removeDocument(doc);
                             }
                             textDocs.push(newDoc);
                         }
@@ -393,10 +332,11 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
                 }
             });
         return textDocs;
-    }
+    };
     /**
-     * used to determine how many cusps and where the cusps are in order
-     * @returns will return an array containing the coordinates of the sharp cusps
+     * Returns array of coordinates corresponding to the sharp cusps in an input stroke
+     * @param points array of X,Y stroke coordinates
+     * @returns array containing the coordinates of the sharp cusps
      */
     getCusps(points: InkData) {
         const arrayOfPoints: { X: number; Y: number }[] = [];
@@ -406,40 +346,13 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
             const point2 = points[i + 1];
             const point3 = points[i + 2];
             if (this.find_angle(point1, point2, point3) < 90) {
+                // NOTE: this is not an accurate way to find cusps -- it is highly dependent on sampling rate and doesn't work well with slowly drawn scribbles
                 arrayOfPoints.push(point2);
             }
         }
         arrayOfPoints.push(points[points.length - 1]);
         return arrayOfPoints;
     }
-    /**
-     * will look through an array of point data and return the coordinates of the smallest box that can fit all the points
-     * @returns the minX,maxX,minY,maxY of the box
-     */
-    getExtremeCoordinates(points: { X: number; Y: number }[]) {
-        const coordinates = points;
-        if (coordinates.length === 0) {
-            throw new Error('Coordinates array is empty');
-        }
-
-        let minX = coordinates[0].X;
-        let maxX = coordinates[0].X;
-        let minY = coordinates[0].Y;
-        let maxY = coordinates[0].Y;
-
-        coordinates.forEach(coord => {
-            if (coord.X < minX) minX = coord.X;
-            if (coord.X > maxX) maxX = coord.X;
-            if (coord.Y < minY) minY = coord.Y;
-            if (coord.Y > maxY) maxY = coord.Y;
-        });
-        return {
-            minX,
-            maxX,
-            minY,
-            maxY,
-        };
-    }
     /**
      * takes in three points and then determines the angle of the points. used to determine if the cusp
      * is sharp enoug
@@ -770,6 +683,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
 }
 
 ScriptingGlobals.add('GestureOverlay', GestureOverlay);
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setPen(width: string, color: string, fill: string, arrowStart: string, arrowEnd: string, dash: string) {
     runInAction(() => {
         GestureOverlay.Instance.SavedColor = ActiveInkColor();
@@ -782,6 +696,7 @@ ScriptingGlobals.add(function setPen(width: string, color: string, fill: string,
         SetActiveDash(dash);
     });
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function resetPen() {
     runInAction(() => {
         SetActiveInkColor(GestureOverlay.Instance.SavedColor ?? 'rgb(0, 0, 0)');
@@ -789,6 +704,7 @@ ScriptingGlobals.add(function resetPen() {
     });
 }, 'resets the pen tool');
 ScriptingGlobals.add(
+    // eslint-disable-next-line prefer-arrow-callback
     function createText(text: string, X: number, Y: number) {
         GestureOverlay.Instance.dispatchGesture(Gestures.Text, [{ X, Y }], text);
     },
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index f04e3eece..177400882 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -49,7 +49,7 @@ import { PinDocView, PinProps } from './PinFuncs';
 import { StyleProp } from './StyleProp';
 import { ViewBoxInterface } from './ViewBoxInterface';
 
-// eslint-disable-next-line @typescript-eslint/no-var-requires
+// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
 const { INK_MASK_SIZE } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
 
 @observer
@@ -107,10 +107,10 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
      * analyzes the ink stroke and saves the analysis of the stroke to the 'inkAnalysis' field,
      * and the recognized words to the 'handwriting'
      */
-    analyzeStrokes=()=> {
+    analyzeStrokes = () => {
         const data: InkData = this.inkScaledData().inkData ?? [];
         CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ['inkAnalysis', 'handwriting'], [data]);
-    }
+    };
 
     /**
      * Toggles whether the ink stroke is displayed as an overlay mask or as a regular stroke.
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3e2c55114..36772e8ec 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -894,7 +894,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 for (let j = 0; j < eraserInkData.length - 3; j += 4) {
                     const intersectCurve: Bezier = InkField.Segment(inkData, i); // other curve
                     const eraserCurve: Bezier = InkField.Segment(eraserInkData, j); // eraser curve
-                    this.bintersects(intersectCurve, eraserCurve).forEach((val: string | number, k: number) => {
+                    InkField.bintersects(intersectCurve, eraserCurve).forEach((val: string | number, k: number) => {
                         // Converting the Bezier.js Split type to a t-value number.
                         const t = +val.toString().split('/')[0];
                         if (k % 2 === 0) {
@@ -1168,26 +1168,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return this.getClosestTs(tVals, excludeT, startIndex, mid - 1);
     };
 
-    // 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) => {
-        // eslint-disable-next-line @typescript-eslint/no-explicit-any
-        if ((curve as any)._linear) {
-            // bezier.js doesn't intersect properly if the curve is actually a line -- so get intersect other curve against this line, then figure out the t coordinates of the intersection on this line
-            const intersections = otherCurve.lineIntersects({ p1: curve.points[0], p2: curve.points[3] });
-            if (intersections.length) {
-                const intPt = otherCurve.get(intersections[0]);
-                const intT = curve.project(intPt).t;
-                return intT ? [intT] : [];
-            }
-        }
-        // eslint-disable-next-line @typescript-eslint/no-explicit-any
-        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.
@@ -1219,7 +1199,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     if (apt.d !== undefined && apt.d < 1 && apt.t !== undefined && !tVals.includes(apt.t)) {
                         tVals.push(apt.t);
                     }
-                    this.bintersects(curve, otherCurve).forEach((val: string | number, ival: number) => {
+                    InkField.bintersects(curve, otherCurve).forEach((val: string | number, ival: number) => {
                         // Converting the Bezier.js Split type to a t-value number.
                         const t = +val.toString().split('/')[0];
                         if (ival % 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/fields/InkField.ts b/src/fields/InkField.ts
index 32abf0076..ad524f73f 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -102,6 +102,26 @@ export class InkField extends ObjectField {
         const top = Math.min(...ys);
         return { right, left, bottom, top, width: right - left, height: bottom - top };
     }
+
+    // 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
+    public static bintersects(curve: Bezier, otherCurve: Bezier) {
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
+        if ((curve as any)._linear) {
+            // bezier.js doesn't intersect properly if the curve is actually a line -- so get intersect other curve against this line, then figure out the t coordinates of the intersection on this line
+            const intersections = otherCurve.lineIntersects({ p1: curve.points[0], p2: curve.points[3] });
+            if (intersections.length) {
+                const intPt = otherCurve.get(intersections[0]);
+                const intT = curve.project(intPt).t;
+                return intT ? [intT] : [];
+            }
+        }
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
+        if ((otherCurve as any)._linear) {
+            return curve.lineIntersects({ p1: otherCurve.points[0], p2: otherCurve.points[3] });
+        }
+        return curve.intersects(otherCurve);
+    }
 }
 
 ScriptingGlobals.add('InkField', InkField);
-- 
cgit v1.2.3-70-g09d2


From b4d9626be5dd3fc158fd05589b5deca59a08dd4f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 1 Oct 2024 14:39:42 -0400
Subject: cleaning up smart drawing menu options.

---
 src/client/views/InkTranscription.tsx              |  4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +-
 .../collections/collectionFreeForm/MarqueeView.tsx | 17 +++---
 src/client/views/global/globalScripts.ts           | 69 +---------------------
 src/client/views/smartdraw/SmartDrawHandler.tsx    |  5 +-
 5 files changed, 14 insertions(+), 85 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index c6b55d55a..b4f4ebf63 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -344,7 +344,7 @@ export class InkTranscription extends React.Component {
         const bounds: { x: number; y: number; width?: number; height?: number }[] = [];
 
         // calculate the necessary bounds from the selected ink docs
-        selected.map(
+        selected.forEach(
             action(d => {
                 const x = NumCast(d.x);
                 const y = NumCast(d.y);
@@ -367,7 +367,7 @@ export class InkTranscription extends React.Component {
         }
 
         // map through all the selected ink strokes and create the groupings
-        selected.map(
+        selected.forEach(
             action(d => {
                 const dx = NumCast(d.x);
                 const dy = NumCast(d.y);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index fe5ced29e..cdcc2328f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1246,9 +1246,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             );
             this._drawing.push(inkDoc);
         });
-        const collection = MarqueeView.getCollection(this._drawing, undefined, true, { left: 1, top: 1, width: 1, height: 1 });
-        console.log('is error here');
-        return collection;
+        return MarqueeView.getCollection(this._drawing, undefined, true, { left: opts.x, top: opts.y, width: 1, height: 1 });
     };
 
     /**
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index f1dba58ce..7614b5337 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -457,20 +457,19 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
         let x_offset = 0;
         let y_offset = 0;
         let row_count = 0;
+        const newColDim = 900;
         for (const label of labelGroups) {
-            const newCollection = MarqueeView.getCollection([], undefined, false, { top: 1, left: 1, width: 1, height: 1 });
-            newCollection._width = 900;
-            newCollection._height = 900;
-            newCollection._x = this.Bounds.left;
-            newCollection._y = this.Bounds.top;
+            const newCollection = MarqueeView.getCollection([], undefined, false, this.Bounds);
+            newCollection._x = this.Bounds.left + x_offset;
+            newCollection._y = this.Bounds.top + y_offset;
+            newCollection._width = newColDim;
+            newCollection._height = newColDim;
             newCollection._freeform_panX = this.Bounds.left + this.Bounds.width / 2;
             newCollection._freeform_panY = this.Bounds.top + this.Bounds.height / 2;
-            newCollection._x = (newCollection._x as number) + x_offset;
-            newCollection._y = (newCollection._y as number) + y_offset;
-            x_offset += (newCollection._width as number) + 40;
+            x_offset += newColDim + 40;
             row_count += 1;
             if (row_count == 3) {
-                y_offset += (newCollection._height as number) + 40;
+                y_offset += newColDim + 40;
                 x_offset = 0;
                 row_count = 0;
             }
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index e9fcb1590..0ab431740 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -1,7 +1,6 @@
 /* eslint-disable @typescript-eslint/no-unused-vars */
 import { Colors } from 'browndash-components';
-import { action, runInAction } from 'mobx';
-import { aggregateBounds } from '../../../Utils';
+import { runInAction } from 'mobx';
 import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
@@ -384,72 +383,6 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?:
     return undefined;
 });
 
-export function createInkGroup(/* inksToGroup?: Doc[], isSubGroup?: boolean */) {
-    // TODO nda - if document being added to is a inkGrouping then we can just add to that group
-    if (Doc.ActiveTool === InkTool.Write) {
-        CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
-            // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
-            const selected = ffView.unprocessedDocs;
-            // loop through selected an get the bound
-            const bounds: { x: number; y: number; width?: number; height?: number }[] = [];
-
-            selected.map(
-                action(d => {
-                    const x = NumCast(d.x);
-                    const y = NumCast(d.y);
-                    const width = NumCast(d._width);
-                    const height = NumCast(d._height);
-                    bounds.push({ x, y, width, height });
-                })
-            );
-
-            const aggregBounds = aggregateBounds(bounds, 0, 0);
-            const marqViewRef = ffView._marqueeViewRef.current;
-
-            // set the vals for bounds in marqueeView
-            if (marqViewRef) {
-                marqViewRef._downX = aggregBounds.x;
-                marqViewRef._downY = aggregBounds.y;
-                marqViewRef._lastX = aggregBounds.r;
-                marqViewRef._lastY = aggregBounds.b;
-            }
-
-            selected.map(
-                action(d => {
-                    const dx = NumCast(d.x);
-                    const dy = NumCast(d.y);
-                    delete d.x;
-                    delete d.y;
-                    delete d.activeFrame;
-                    delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
-                    delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
-                    // calculate pos based on bounds
-                    if (marqViewRef?.Bounds) {
-                        d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2;
-                        d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2;
-                    }
-                    return d;
-                })
-            );
-            ffView._props.removeDocument?.(selected);
-            // TODO: nda - this is the code to actually get a new grouped collection
-            const newCollection = MarqueeView.getCollection(selected, undefined, true, { top: 1, left: 1, width: 1, height: 1 });
-            if (newCollection) {
-                newCollection.height = NumCast(newCollection._height);
-                newCollection.width = NumCast(newCollection._width);
-            }
-
-            // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
-            newCollection && ffView._props.addDocument?.(newCollection);
-            // TODO: nda - will probably need to go through and only remove the unprocessed selected docs
-            ffView.unprocessedDocs = [];
-
-            // InkTranscription.Instance.transcribeInk(newCollection, selected, false);
-        });
-    }
-    CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
-}
-
 function setActiveTool(toolIn: InkTool | Gestures, keepPrim: boolean, checkResult?: boolean) {
     InkTranscription.Instance?.createInkGroup();
     const tool = toolIn === InkTool.Eraser ? Doc.UserDoc().activeEraserTool : toolIn;
diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx
index a77092a1a..6d1277a08 100644
--- a/src/client/views/smartdraw/SmartDrawHandler.tsx
+++ b/src/client/views/smartdraw/SmartDrawHandler.tsx
@@ -228,9 +228,8 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
             console.error('GPT call failed');
             return;
         }
-        console.log(res);
         const strokeData = await this.parseSvg(res, startPt, false, autoColor);
-        const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData?.data, strokeData?.lastInput, strokeData?.lastRes);
+        const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes);
         drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res);
 
         this._errorOccurredOnce = false;
@@ -261,7 +260,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
             }
             const strokeData = await this.parseSvg(res, { X: this._lastInput.x, Y: this._lastInput.y }, true, lastInput?.autoColor || this._autoColor);
             this.RemoveDrawing !== unimplementedFunction && this.RemoveDrawing(true, this._selectedDoc);
-            const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData?.data, strokeData?.lastInput, strokeData?.lastRes);
+            const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes);
             drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res);
             return strokeData;
         } catch (err) {
-- 
cgit v1.2.3-70-g09d2


From 8d7bf0588ca63c2d505a494865dcf9921eb1383d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 1 Oct 2024 15:57:57 -0400
Subject: more lint fixes

---
 src/client/util/CurrentUserUtils.ts                   |  2 +-
 src/client/util/DropConverter.ts                      |  2 +-
 src/client/util/Scripting.ts                          |  5 -----
 src/client/views/DocumentDecorations.tsx              |  2 +-
 src/client/views/InkTranscription.tsx                 |  1 +
 src/client/views/InkingStroke.tsx                     |  4 +---
 src/client/views/LightboxView.tsx                     |  2 --
 src/client/views/Main.tsx                             |  1 -
 src/client/views/MainView.tsx                         | 19 +++++++------------
 .../views/collections/CollectionCarousel3DView.tsx    |  2 +-
 .../views/collections/CollectionCarouselView.tsx      |  4 ++--
 .../collectionFreeForm/CollectionFreeFormView.tsx     | 11 ++++++-----
 .../collections/collectionFreeForm/MarqueeView.tsx    |  3 +--
 src/client/views/nodes/FontIconBox/FontIconBox.tsx    |  1 -
 src/client/views/nodes/ImageBox.tsx                   |  1 -
 src/client/views/nodes/PDFBox.tsx                     |  5 +----
 .../views/nodes/formattedText/FormattedTextBox.tsx    |  8 --------
 src/client/views/nodes/formattedText/RichTextMenu.tsx |  1 -
 src/client/views/pdf/PDFViewer.tsx                    |  2 --
 src/client/views/smartdraw/AnnotationPalette.tsx      |  4 +---
 src/client/views/smartdraw/SmartDrawHandler.tsx       |  3 ++-
 src/fields/SchemaHeaderField.ts                       |  1 -
 22 files changed, 26 insertions(+), 58 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 59760ba32..96d69e7a1 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -2,7 +2,7 @@
 import { reaction, runInAction } from "mobx";
 import * as rp from 'request-promise';
 import { ClientUtils, OmitKeys } from "../../ClientUtils";
-import { Doc, DocListCast, DocListCastAsync, FieldType, Opt, StrListCast } from "../../fields/Doc";
+import { Doc, DocListCast, DocListCastAsync, FieldType, Opt } from "../../fields/Doc";
 import { DocData } from "../../fields/DocSymbols";
 import { InkTool } from "../../fields/InkField";
 import { List } from "../../fields/List";
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 71cdaa58b..b5d29be4c 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -67,7 +67,7 @@ export function MakeTemplate(doc: Doc) {
 /**
  * Makes a draggable button or image that will create a template doc Instance
  */
-export function makeUserTemplateButtonOrImage(doc: Doc, image: string | undefined) {
+export function makeUserTemplateButtonOrImage(doc: Doc, image?: string) {
     const layoutDoc = doc; // doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
     if (layoutDoc.type !== DocumentType.FONTICON) {
         !layoutDoc.isTemplateDoc && makeTemplate(layoutDoc);
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index c63d3d7cb..c7b86815a 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -60,7 +60,6 @@ function Run(script: string | undefined, customParams: string[], diagnostics: ts
     // let params: any[] = [Docs, ...fieldTypes];
     const compiledFunction = (() => {
         try {
-            // eslint-disable-next-line no-new-func
             return new Function(...paramNames, `return ${script}`);
         } catch (e) {
             console.log(e);
@@ -69,10 +68,8 @@ function Run(script: string | undefined, customParams: string[], diagnostics: ts
     })();
     if (!compiledFunction) return { compiled: false, errors };
     const { capturedVariables = {} } = options;
-    // eslint-disable-next-line default-param-last
     const run = (args: { [name: string]: unknown } = {}, onError?: (e: string) => void, errorVal?: ts.Diagnostic): ScriptResult => {
         const argsArray: unknown[] = [];
-        // eslint-disable-next-line no-restricted-syntax
         for (const name of customParams) {
             if (name !== 'this') {
                 argsArray.push(name in args ? args[name] : capturedVariables[name]);
@@ -224,7 +221,6 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
     if ('this' in params || 'this' in capturedVariables) {
         paramNames.push('this');
     }
-    // eslint-disable-next-line no-restricted-syntax
     for (const key in params) {
         if (key !== 'this') {
             paramNames.push(key);
@@ -234,7 +230,6 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
         const val = params[key];
         return `${key}: ${val}`;
     });
-    // eslint-disable-next-line no-restricted-syntax
     for (const key in capturedVariables) {
         if (key !== 'this') {
             const val = capturedVariables[key];
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e569c7dc8..1c0d51e17 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { IconButton } from 'browndash-components';
-import { action, computed, makeObservable, observable, runInAction, trace } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { FaUndo } from 'react-icons/fa';
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index b4f4ebf63..24d53a8c8 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -18,6 +18,7 @@ import { URLField } from '../../fields/URLField';
  * Class component that handles inking in writing mode
  */
 export class InkTranscription extends React.Component {
+    // eslint-disable-next-line no-use-before-define
     static Instance: InkTranscription;
 
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index f44c1720d..270266a94 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -51,7 +51,7 @@ import { InkTranscription } from './InkTranscription';
 import { CollectionFreeFormView } from './collections/collectionFreeForm';
 import { DocumentView } from './nodes/DocumentView';
 
-// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
+// eslint-disable-next-line @typescript-eslint/no-require-imports
 const { INK_MASK_SIZE } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
 
 @observer
@@ -473,7 +473,6 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
                         // mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
                         cursor: this._props.isSelected() ? 'default' : undefined,
                     }}
-                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...interactions}>
                     {clickableLine(this.onPointerDown, isInkMask)}
                     {isInkMask ? null : inkLine}
@@ -490,7 +489,6 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
                             // top: (this._props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this._props.NativeDimScaling?.() || 1)) / 2,
                         }}>
                         <FormattedTextBox
-                            // eslint-disable-next-line react/jsx-props-no-spreading
                             {...this._props}
                             setHeight={undefined}
                             setContentViewBox={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text)
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 331b3db64..a543b4875 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -46,7 +46,6 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
         ); 
     } // prettier-ignore
     public static LightboxDoc = () => LightboxView.Instance?._doc;
-    // eslint-disable-next-line no-use-before-define
     static Instance: LightboxView;
     private _path: {
         doc: Opt<Doc>; //
@@ -341,7 +340,6 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
 }
 interface LightboxTourBtnProps {
     navBtn: (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: IconProp, display: boolean, click: () => void, color?: string) => JSX.Element;
-    // eslint-disable-next-line react/no-unused-prop-types
     future: () => Opt<Doc[]>;
     stepInto: () => void;
     lightboxDoc: () => Opt<Doc>;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 94294e97a..73d2872d1 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable no-new */
 // if ((module as any).hot) {
 //     (module as any).hot.accept();
 // }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 8e921ca5e..fa2e94d12 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -3,7 +3,7 @@ import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons';
 import * as far from '@fortawesome/free-regular-svg-icons';
 import * as fa from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, configure, makeObservable, observable, reaction, runInAction, trace } from 'mobx';
+import { action, computed, configure, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import ResizeObserver from 'resize-observer-polyfill';
@@ -77,7 +77,7 @@ import { TopBar } from './topbar/TopBar';
 import { SmartDrawHandler } from './smartdraw/SmartDrawHandler';
 import { InkTranscription } from './InkTranscription';
 
-// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
+// eslint-disable-next-line @typescript-eslint/no-require-imports
 const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
 
 @observer
@@ -977,13 +977,11 @@ export class MainView extends ObservableReactComponent<object> {
             <div className="mainView-snapLines">
                 <svg style={{ width: '100%', height: '100%' }}>
                     {[
-                        ...SnappingManager.HorizSnapLines.map((l, i) => (
-                            // eslint-disable-next-line react/no-array-index-key
-                            <line key={'horiz' + i} x1="0" y1={l} x2="2000" y2={l} stroke={lightOrDark(StrCast(dragPar.layoutDoc.backgroundColor, 'gray'))} opacity={0.3} strokeWidth={1} strokeDasharray="2 2" />
+                        ...SnappingManager.HorizSnapLines.map(l => (
+                            <line key={'horiz' + l} x1="0" y1={l} x2="2000" y2={l} stroke={lightOrDark(StrCast(dragPar.layoutDoc.backgroundColor, 'gray'))} opacity={0.3} strokeWidth={1} strokeDasharray="2 2" />
                         )),
-                        ...SnappingManager.VertSnapLines.map((l, i) => (
-                            // eslint-disable-next-line react/no-array-index-key
-                            <line key={'vert' + i} y1={this.topOfMainDocContent.toString()} x1={l} y2="2000" x2={l} stroke={lightOrDark(StrCast(dragPar.layoutDoc.backgroundColor, 'gray'))} opacity={0.3} strokeWidth={1} strokeDasharray="2 2" />
+                        ...SnappingManager.VertSnapLines.map(l => (
+                            <line key={'vert' + l} y1={this.topOfMainDocContent.toString()} x1={l} y2="2000" x2={l} stroke={lightOrDark(StrCast(dragPar.layoutDoc.backgroundColor, 'gray'))} opacity={0.3} strokeWidth={1} strokeDasharray="2 2" />
                         )),
                     ]}
                 </svg>
@@ -1071,10 +1069,7 @@ export class MainView extends ObservableReactComponent<object> {
                         docView={DocButtonState.Instance.LinkEditorDocView}
                     />
                 ) : null}
-                {LinkInfo.Instance?.LinkInfo ? (
-                    // eslint-disable-next-line react/jsx-props-no-spreading
-                    <LinkDocPreview {...LinkInfo.Instance.LinkInfo} />
-                ) : null}
+                {LinkInfo.Instance?.LinkInfo ? <LinkDocPreview {...LinkInfo.Instance.LinkInfo} /> : null}
                 {((page: string) => {
                     // prettier-ignore
                     switch (page) {
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index cf86a0a4e..c5da8e037 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -16,6 +16,7 @@ import './CollectionCarousel3DView.scss';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
 import { Transform } from '../../util/Transform';
 
+// eslint-disable-next-line @typescript-eslint/no-require-imports
 const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss');
 
 @observer
@@ -88,7 +89,6 @@ export class CollectionCarousel3DView extends CollectionSubView() {
         const currentIndex = NumCast(this.layoutDoc._carousel_index);
         const displayDoc = (childPair: { layout: Doc; data: Doc }, dxf: () => Transform) => (
             <DocumentView
-                // eslint-disable-next-line react/jsx-props-no-spreading
                 {...this._props}
                 Document={childPair.layout}
                 TemplateDataDocument={childPair.data}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index db75c9e8b..74cf580c9 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -3,8 +3,8 @@ import { IReactionDisposer, action, computed, makeObservable, observable, reacti
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { StopEvent, returnOne, returnZero } from '../../../ClientUtils';
-import { Doc, DocListCast, Opt } from '../../../fields/Doc';
-import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { Doc, Opt } from '../../../fields/Doc';
+import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DragManager } from '../../util/DragManager';
 import { ContextMenu } from '../ContextMenu';
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index cdcc2328f..0cc63d632 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -510,12 +510,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         e.stopPropagation();
                         break;
                     case InkTool.SmartDraw:
-                        setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, e => this.showSmartDraw(e.pageX, e.pageY), hit !== -1);
+                        setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, () => this.showSmartDraw(e.pageX, e.pageY), hit !== -1);
                         e.stopPropagation();
+                        break;
                     case InkTool.None:
                         if (!(this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
-                            const hit = this._clusters.handlePointerDown(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY));
-                            setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, hit !== -1, false);
+                            const ahit = this._clusters.handlePointerDown(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY));
+                            setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, ahit !== -1, false);
                         }
                         break;
                     default:
@@ -655,13 +656,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     @action
-    onEraserClick = (e: PointerEvent, doubleTap?: boolean) => {
+    onEraserClick = (e: PointerEvent) => {
         e.preventDefault();
         e.stopImmediatePropagation();
         this.erase(e, [0, 0]);
     };
 
-    forceStrokeGesture = (e: PointerEvent, gesture: Gestures, points: InkData, text?: any) => {
+    forceStrokeGesture = (e: PointerEvent, gesture: Gestures, points: InkData, text?: string) => {
         this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, InkField.getBounds(points), text));
     };
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 7614b5337..b1f6815b3 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -57,6 +57,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
         return { left: NumCast(pinDoc._freeform_panX) - panelWidth / 2 / ps, top: NumCast(pinDoc._freeform_panY) - panelHeight / 2 / ps, width: panelWidth / ps, height: panelHeight / ps };
     }
 
+    // eslint-disable-next-line no-use-before-define
     static Instance: MarqueeView;
 
     constructor(props: SubCollectionViewProps & MarqueeViewProps) {
@@ -548,7 +549,6 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
     };
 
     touchesLine(r1: { left: number; top: number; width: number; height: number }) {
-        // eslint-disable-next-line no-restricted-syntax
         for (const lassoPt of this._lassoPts) {
             const topLeft = this.Transform.transformPoint(lassoPt[0], lassoPt[1]);
             if (r1.left < topLeft[0] && topLeft[0] < r1.left + r1.width && r1.top < topLeft[1] && topLeft[1] < r1.top + r1.height) {
@@ -569,7 +569,6 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
             let hasLeft = false;
             let hasBottom = false;
             let hasRight = false;
-            // eslint-disable-next-line no-restricted-syntax
             for (const lassoPt of this._lassoPts) {
                 const truePoint = this.Transform.transformPoint(lassoPt[0], lassoPt[1]);
                 hasLeft = hasLeft || (truePoint[0] > tl[0] && truePoint[0] < r1.left && truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height);
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index b7e1350ca..feaf84b7b 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/jsx-props-no-spreading */
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Button, ColorPicker, Dropdown, DropdownType, IconButton, IListItemProps, MultiToggle, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index aa4376bb2..ec5e062c8 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -493,7 +493,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 }}>
                 <CollectionFreeFormView
                     ref={this._ffref}
-                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this._props}
                     setContentViewBox={emptyFunction}
                     NativeWidth={returnZero}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 42ac51107..7ef431885 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -10,7 +10,7 @@ import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { ComputedField } from '../../../fields/ScriptField';
-import { Cast, FieldValue, ImageCast, NumCast, StrCast, toList } from '../../../fields/Types';
+import { Cast, FieldValue, NumCast, StrCast, toList } from '../../../fields/Types';
 import { ImageField, PdfField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction } from '../../../Utils';
@@ -538,7 +538,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             return ComponentTag === CollectionStackingView ? (
                 <SidebarAnnos
                     ref={this._sidebarRef}
-                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this._props}
                     Document={this.Document}
                     layoutDoc={this.layoutDoc}
@@ -554,7 +553,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             ) : (
                 <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this._props.select(false), true)}>
                     <ComponentTag
-                        // eslint-disable-next-line react/jsx-props-no-spreading
                         {...this._props}
                         setContentViewBox={emptyFunction} // override setContentView to do nothing
                         NativeWidth={this.sidebarNativeWidthFunc}
@@ -608,7 +606,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         top: 0,
                     }}>
                     <PDFViewer
-                        // eslint-disable-next-line react/jsx-props-no-spreading
                         {...this._props}
                         pdfBox={this}
                         sidebarAddDoc={this.sidebarAddDocument}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d1b0dc7bf..d3bc08bd3 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -655,7 +655,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                         textContent += node.child(i).textContent;
                         i++;
                     }
-                    // eslint-disable-next-line no-cond-assign
                     while (ep && (foundAt = textContent.slice(index).search(regexp)) > -1) {
                         const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + 1), pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + find.length + 1));
                         ret.push(sel);
@@ -714,7 +713,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             const hr = Math.round(Date.now() / 1000 / 60 / 60);
             numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
         }
-        // eslint-disable-next-line operator-assignment
         this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css changes happen outside of react/mobx.  so we need to set a flag that will notify anyone interested in layout changes triggered by css changes (eg., CollectionLinkView)
     };
 
@@ -1131,7 +1129,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             const nodes: Node[] = [];
             let hadStart = start !== 0;
             frag.forEach((node, index) => {
-                // eslint-disable-next-line no-use-before-define
                 const examinedNode = findAnchorNode(node, editor);
                 if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this._editorView?.state.schema.nodes.dashDoc || examinedNode.node.type === this._editorView?.state.schema.nodes.audiotag)) {
                     nodes.push(examinedNode.node);
@@ -1285,7 +1282,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             action(selected => {
                 this.prepareForTyping();
                 if (FormattedTextBox._globalHighlights.has('Bold Text')) {
-                    // eslint-disable-next-line operator-assignment
                     this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed
                 }
                 if (RichTextMenu.Instance?.view === this._editorView && !selected) {
@@ -1711,7 +1707,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         const match = RTFCast(this.Document[this.fieldKey])?.Text.match(/^(@[a-zA-Z][a-zA-Z_0-9 -]*[a-zA-Z_0-9-]+)/);
         if (match) {
             this.dataDoc.title_custom = true;
-            // eslint-disable-next-line prefer-destructuring
             this.dataDoc.title = match[1]; // this triggers the collectionDockingView to publish this Doc
             this.EditorView?.dispatch(this.EditorView?.state.tr.deleteRange(0, match[1].length + 1));
         }
@@ -1793,7 +1788,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
         const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
         if (children && !SnappingManager.IsDragging) {
-            // eslint-disable-next-line no-use-before-define
             const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0;
             const toNum = (val: string) => Number(val.replace('px', ''));
             const toHgt = (node: Element): number => {
@@ -1880,7 +1874,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             return ComponentTag === CollectionStackingView ? (
                 <SidebarAnnos
                     ref={this._sidebarRef}
-                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this._props}
                     Document={this.Document}
                     layoutDoc={this.layoutDoc}
@@ -1900,7 +1893,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             ) : (
                 <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => DocumentView.SelectView(this.DocumentView?.(), false), true)}>
                     <ComponentTag
-                        // eslint-disable-next-line react/jsx-props-no-spreading
                         {...this._props}
                         ref={this._sidebarTagRef}
                         setContentView={emptyFunction}
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 1560d51a1..88e2e4248 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -697,7 +697,6 @@ interface RichTextMenuPluginProps {
     editorProps: FormattedTextBoxProps;
 }
 export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps> {
-    // eslint-disable-next-line react/no-unused-class-component-methods
     update(view: EditorView & { TextView?: FormattedTextBox }, lastState: EditorState | undefined) {
         RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, view.TextView?.layoutDoc);
     }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 01242ba48..7a86ee802 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -504,7 +504,6 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
         return (
             <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this._props.Document), transform: `scale(${NumCast(this._props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
                 {inlineAnnos.map(anno => (
-                    // eslint-disable-next-line react/jsx-props-no-spreading
                     <Annotation {...this._props} fieldKey={this._props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} containerDataDoc={this._props.dataDoc} annoDoc={anno} key={`${anno[Id]}-annotation`} />
                 ))}
             </div>
@@ -537,7 +536,6 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                 pointerEvents: Doc.ActiveTool !== InkTool.None ? 'all' : undefined,
             }}>
             <CollectionFreeFormView
-                // eslint-disable-next-line react/jsx-props-no-spreading
                 {...this._props}
                 NativeWidth={returnZero}
                 NativeHeight={returnZero}
diff --git a/src/client/views/smartdraw/AnnotationPalette.tsx b/src/client/views/smartdraw/AnnotationPalette.tsx
index 1c17469c3..f1e2e4f41 100644
--- a/src/client/views/smartdraw/AnnotationPalette.tsx
+++ b/src/client/views/smartdraw/AnnotationPalette.tsx
@@ -11,6 +11,7 @@ import { emptyFunction } from '../../../Utils';
 import { Doc, DocListCast, returnEmptyDoclist } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { ImageCast, NumCast } from '../../../fields/Types';
+import { ImageField } from '../../../fields/URLField';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { Docs } from '../../documents/Documents';
 import { makeUserTemplateButtonOrImage } from '../../util/DropConverter';
@@ -23,8 +24,6 @@ import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
 import { FieldView } from '../nodes/FieldView';
 import './AnnotationPalette.scss';
 import { DrawingOptions, SmartDrawHandler } from './SmartDrawHandler';
-import { ImageField } from '../../../fields/URLField';
-import { Copy } from '../../../fields/FieldSymbols';
 
 interface AnnotationPaletteProps {
     Document: Doc;
@@ -158,7 +157,6 @@ export class AnnotationPalette extends ObservableReactComponent<AnnotationPalett
                 console.log('Error generating drawing', e);
             }
         }
-        // eslint-disable-next-line @typescript-eslint/no-unused-expressions
         this._opts.text !== '' ? (this._opts.text = `${this._opts.text} ~~~ ${this._userInput}`) : (this._opts.text = this._userInput);
         this._userInput = '';
         this._isLoading = false;
diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx
index 76bfe2a19..75ef55060 100644
--- a/src/client/views/smartdraw/SmartDrawHandler.tsx
+++ b/src/client/views/smartdraw/SmartDrawHandler.tsx
@@ -50,6 +50,7 @@ export interface DrawingOptions {
 
 @observer
 export class SmartDrawHandler extends ObservableReactComponent<object> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: SmartDrawHandler;
 
     private _lastInput: DrawingOptions = { text: '', complexity: 5, size: 350, autoColor: true, x: 0, y: 0 };
@@ -395,7 +396,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
                                 defaultChecked={true}
                                 value={this._autoColor}
                                 size="small"
-                                onChange={action(e => this._canInteract && (this._autoColor = !this._autoColor))}
+                                onChange={action(() => this._canInteract && (this._autoColor = !this._autoColor))}
                             />
                         </div>
                         <div className="complexity">
diff --git a/src/fields/SchemaHeaderField.ts b/src/fields/SchemaHeaderField.ts
index 0a8dd1d9e..5f4d59cf9 100644
--- a/src/fields/SchemaHeaderField.ts
+++ b/src/fields/SchemaHeaderField.ts
@@ -79,7 +79,6 @@ export class SchemaHeaderField extends ObjectField {
     @serializable(primitive())
     desc: boolean | undefined; // boolean determines sort order, undefined when no sort
 
-    // eslint-disable-next-line default-param-last
     constructor(heading: string = '', color: string = RandomPastel(), type?: ColumnType, width?: number, desc?: boolean, collapsed?: boolean) {
         super();
 
-- 
cgit v1.2.3-70-g09d2

