From fd5278045e8c2e280d81cb965c0b2cc5afb59be8 Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Wed, 7 Aug 2024 18:09:47 -0400 Subject: problem with setting smooth amount --- src/client/views/InkStrokeProperties.ts | 43 +++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 3920ecc2a..35d628a4e 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -5,8 +5,8 @@ import { Doc, NumListCast, Opt } from '../../fields/Doc'; import { InkData, InkField, InkTool } from '../../fields/InkField'; import { List } from '../../fields/List'; import { listSpec } from '../../fields/Schema'; -import { Cast, NumCast } from '../../fields/Types'; -import { PointData } from '../../pen-gestures/GestureTypes'; +import { Cast, NumCast, toList } from '../../fields/Types'; +import { Gestures, PointData } from '../../pen-gestures/GestureTypes'; import { Point } from '../../pen-gestures/ndollar'; import { DocumentType } from '../documents/DocumentTypes'; import { undoBatch } from '../util/UndoManager'; @@ -14,6 +14,9 @@ import { FitOneCurve } from '../util/bezierFit'; import { InkingStroke } from './InkingStroke'; import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { DocumentView } from './nodes/DocumentView'; +import simplify from 'simplify-js'; +import { GestureUtils } from '../../pen-gestures/GestureUtils'; +import { GestureOverlay } from './GestureOverlay'; export class InkStrokeProperties { // eslint-disable-next-line no-use-before-define @@ -487,4 +490,40 @@ export class InkStrokeProperties { } return inkCopy; }); + + @undoBatch + smoothInkStrokes = (inkDocs: Doc[], tolerance: number = 5) => { + inkDocs.forEach(inkDoc => { + const inkView = DocumentView.getDocumentView(inkDoc); + const inkStroke = inkView?.ComponentView as InkingStroke; + const { inkData } = inkStroke.inkScaledData(); + + const result = inkData.length > 2 && GestureUtils.GestureRecognizer.Recognize([inkData]); + console.log(result); + let polygonPoints: { X: number; Y: number }[] | undefined = undefined; + if (result && (result.Name === 'line' ? result.Score > 0.92 : result.Score > 0.85)) { + switch (result.Name) { + case Gestures.Line: + case Gestures.Triangle: + case Gestures.Rectangle: + case Gestures.Circle: + GestureOverlay.makeBezierPolygon(inkData, result.Name, true); + break; + default: + } + } else { + const polylinePoints = inkData.filter((pt, index) => { return index % 4 === 0 || pt === inkData.lastElement()}).map(pt => { return { x: pt.X, y: pt.Y }; }); // prettier-ignore + if (polylinePoints.length > 2) { + const toKeep = simplify(polylinePoints, tolerance).map(pt => {return { X: pt.x, Y: pt.y }}); // prettier-ignore + for (var i = 4; i < inkData.length - 3; i += 4) { + const contains = toKeep.find(pt => pt.X === inkData[i].X && pt.Y === inkData[i].Y); + if (!contains) { + this._currentPoint = i; + inkView && this.deletePoints(inkView, false); + } + } + } + } + }); + }; } -- cgit v1.2.3-70-g09d2 From 6f73686ec4dc3e01ae3eacc0150aa59eafea0325 Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Tue, 27 Aug 2024 16:22:33 -0400 Subject: pulling from master --- eslint.config.mjs | 17 +- package-lock.json | 195 ++++++++++----------- package.json | 8 +- src/client/util/DropConverter.ts | 4 + src/client/views/InkStrokeProperties.ts | 7 +- src/client/views/MarqueeAnnotator.tsx | 145 --------------- src/client/views/PropertiesView.scss | 2 +- src/client/views/PropertiesView.tsx | 52 +++--- .../collectionFreeForm/CollectionFreeFormView.tsx | 64 +++---- .../collectionFreeForm/MarqueeOptionsMenu.tsx | 2 - .../collections/collectionFreeForm/MarqueeView.tsx | 65 ------- src/client/views/pdf/AnchorMenu.tsx | 20 +-- src/client/views/smartdraw/AnnotationPalette.tsx | 13 ++ src/client/views/smartdraw/SmartDrawHandler.tsx | 22 ++- 14 files changed, 213 insertions(+), 403 deletions(-) (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/eslint.config.mjs b/eslint.config.mjs index b35601abd..c69209327 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,6 +1,13 @@ -import pluginJs from '@eslint/js'; -import pluginReactConfig from 'eslint-plugin-react/configs/recommended.js'; -import globals from 'globals'; -import tseslint from 'typescript-eslint'; +import globals from "globals"; +import pluginJs from "@eslint/js"; +import tseslint from "typescript-eslint"; +import pluginReact from "eslint-plugin-react"; -export default [{ languageOptions: { globals: { ...globals.browser, ...globals.node } } }, pluginJs.configs.recommended, ...tseslint.configs.recommended, pluginReactConfig]; + +export default [ + {files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"]}, + {languageOptions: { globals: globals.browser }}, + pluginJs.configs.recommended, + ...tseslint.configs.recommended, + pluginReact.configs.flat.recommended, +]; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7f6237ef5..95fb8ce11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -246,7 +246,7 @@ "xregexp": "^5.1.1" }, "devDependencies": { - "@eslint/js": "^9.1.1", + "@eslint/js": "^9.9.1", "@types/adm-zip": "^0.5.5", "@types/animejs": "^3.1.12", "@types/archiver": "^6.0.2", @@ -301,9 +301,9 @@ "eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^5.0.1", - "eslint-plugin-react": "^7.34.1", + "eslint-plugin-react": "^7.35.0", "eslint-plugin-react-hooks": "^4.6.0", - "globals": "^15.1.0", + "globals": "^15.9.0", "jsdom": "^24.0.0", "mocha": "^10.2.0", "prettier": "^3.1.0", @@ -312,7 +312,7 @@ "ts-loader": "^9.5.1", "ts-node": "^10.9.1", "ts-node-dev": "^2.0.0", - "typescript-eslint": "^7.8.0", + "typescript-eslint": "^7.18.0", "webpack-dev-server": "^5.0.4" }, "engines": { @@ -2637,10 +2637,11 @@ } }, "node_modules/@eslint/js": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.6.0.tgz", - "integrity": "sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A==", + "version": "9.9.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.1.tgz", + "integrity": "sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -9946,16 +9947,17 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.0.tgz", - "integrity": "sha512-py1miT6iQpJcs1BiJjm54AMzeuMPBSPuKPlnT8HlfudbcS5rYeX5jajpLf3mrdRh9dA/Ec2FVUY0ifeVNDIhZw==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.16.0", - "@typescript-eslint/type-utils": "7.16.0", - "@typescript-eslint/utils": "7.16.0", - "@typescript-eslint/visitor-keys": "7.16.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -9979,14 +9981,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.0.tgz", - "integrity": "sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw==", - "dependencies": { - "@typescript-eslint/scope-manager": "7.16.0", - "@typescript-eslint/types": "7.16.0", - "@typescript-eslint/typescript-estree": "7.16.0", - "@typescript-eslint/visitor-keys": "7.16.0", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" }, "engines": { @@ -10006,12 +10009,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.0.tgz", - "integrity": "sha512-8gVv3kW6n01Q6TrI1cmTZ9YMFi3ucDT7i7aI5lEikk2ebk1AEjrwX8MDTdaX5D7fPXMBLvnsaa0IFTAu+jcfOw==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.0", - "@typescript-eslint/visitor-keys": "7.16.0" + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -10022,13 +10026,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.0.tgz", - "integrity": "sha512-j0fuUswUjDHfqV/UdW6mLtOQQseORqfdmoBNDFOqs9rvNVR2e+cmu6zJu/Ku4SDuqiJko6YnhwcL8x45r8Oqxg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.16.0", - "@typescript-eslint/utils": "7.16.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -10049,9 +10054,10 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.0.tgz", - "integrity": "sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "license": "MIT", "engines": { "node": "^18.18.0 || >=20.0.0" }, @@ -10061,12 +10067,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.0.tgz", - "integrity": "sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "7.16.0", - "@typescript-eslint/visitor-keys": "7.16.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -10088,9 +10095,10 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -10099,15 +10107,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.0.tgz", - "integrity": "sha512-PqP4kP3hb4r7Jav+NiRCntlVzhxBNWq6ZQ+zQwII1y/G/1gdIPeYDCKr2+dH6049yJQsWZiHU6RlwvIFBXXGNA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.16.0", - "@typescript-eslint/types": "7.16.0", - "@typescript-eslint/typescript-estree": "7.16.0" + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -10121,11 +10130,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.0.tgz", - "integrity": "sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.0", + "@typescript-eslint/types": "7.18.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -10891,18 +10901,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.toreversed": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", - "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - } - }, "node_modules/array.prototype.tosorted": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", @@ -17072,6 +17070,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "license": "MIT", "dependencies": { "path-type": "^4.0.0" }, @@ -17618,6 +17617,7 @@ "version": "8.57.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -18884,35 +18884,36 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.34.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.3.tgz", - "integrity": "sha512-aoW4MV891jkUulwDApQbPYTVZmeuSyFrudpbTAQuj5Fv8VL+o6df2xIGpw8B0hPjAaih1/Fb0om9grCdyFYemA==", + "version": "7.35.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz", + "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==", "dev": true, + "license": "MIT", "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.2", - "array.prototype.toreversed": "^1.1.2", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.0.19", "estraverse": "^5.3.0", + "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.8", "object.fromentries": "^2.0.8", - "object.hasown": "^1.1.4", "object.values": "^1.2.0", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.11" + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "node_modules/eslint-plugin-react-hooks": { @@ -20855,10 +20856,11 @@ } }, "node_modules/globals": { - "version": "15.8.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.8.0.tgz", - "integrity": "sha512-VZAJ4cewHTExBWDHR6yptdIBlx9YSSZuwojj9Nt5mBRXQzrKakDsVKQ1J63sklLvzAJm0X5+RpO4i3Y2hcOnFw==", + "version": "15.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz", + "integrity": "sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -20886,6 +20888,7 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "license": "MIT", "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -34178,23 +34181,6 @@ "node": ">= 0.4" } }, - "node_modules/object.hasown": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz", - "integrity": "sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object.values": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", @@ -38422,6 +38408,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", "engines": { "node": ">=8" } @@ -38983,6 +38970,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", @@ -40552,14 +40550,15 @@ "integrity": "sha512-7sI4e/bZijOzyURng88oOFZCISQPTHozfE2sUu5AviFYk5QV7fYGb6YiDl+vKjF/pICA354JImBImL9XJWUvdQ==" }, "node_modules/typescript-eslint": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.16.0.tgz", - "integrity": "sha512-kaVRivQjOzuoCXU6+hLnjo3/baxyzWVO5GrnExkFzETRYJKVHYkrJglOu2OCm8Hi9RPDWX1PTNNTpU5KRV0+RA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.18.0.tgz", + "integrity": "sha512-PonBkP603E3tt05lDkbOMyaxJjvKqQrXsnow72sVeOFINDE/qNmnnd+f9b4N+U7W6MXnnYyrhtmF2t08QWwUbA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "7.16.0", - "@typescript-eslint/parser": "7.16.0", - "@typescript-eslint/utils": "7.16.0" + "@typescript-eslint/eslint-plugin": "7.18.0", + "@typescript-eslint/parser": "7.18.0", + "@typescript-eslint/utils": "7.18.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" diff --git a/package.json b/package.json index 16b2841be..8753e995b 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "tsc": "tsc -t es5" }, "devDependencies": { - "@eslint/js": "^9.1.1", + "@eslint/js": "^9.9.1", "@types/adm-zip": "^0.5.5", "@types/animejs": "^3.1.12", "@types/archiver": "^6.0.2", @@ -78,9 +78,9 @@ "eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^5.0.1", - "eslint-plugin-react": "^7.34.1", + "eslint-plugin-react": "^7.35.0", "eslint-plugin-react-hooks": "^4.6.0", - "globals": "^15.1.0", + "globals": "^15.9.0", "jsdom": "^24.0.0", "mocha": "^10.2.0", "prettier": "^3.1.0", @@ -89,7 +89,7 @@ "ts-loader": "^9.5.1", "ts-node": "^10.9.1", "ts-node-dev": "^2.0.0", - "typescript-eslint": "^7.8.0", + "typescript-eslint": "^7.18.0", "webpack-dev-server": "^5.0.4" }, "dependencies": { diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index 3e26fefad..aa7268b61 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -87,6 +87,10 @@ export function makeUserTemplateButton(doc: Doc) { return dbox; } +/** + * Similar to makeUserTemplateButton, but rather than creating a draggable button for the template, it takes in + * an ImageField that will display. + */ export function makeUserTemplateImage(doc: Doc, image: ImageField) { const layoutDoc = doc; // doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc; if (layoutDoc.type !== DocumentType.FONTICON) { diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 35d628a4e..ffda126f4 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -491,6 +491,12 @@ export class InkStrokeProperties { return inkCopy; }); + /** + * Function that "smooths" ink strokes by using the gesture recognizer to detect shapes and + * removing excess control points with the simplify-js package. + * @param inkDocs + * @param tolerance Determines how strong the smooth effect will be + */ @undoBatch smoothInkStrokes = (inkDocs: Doc[], tolerance: number = 5) => { inkDocs.forEach(inkDoc => { @@ -500,7 +506,6 @@ export class InkStrokeProperties { const result = inkData.length > 2 && GestureUtils.GestureRecognizer.Recognize([inkData]); console.log(result); - let polygonPoints: { X: number; Y: number }[] | undefined = undefined; if (result && (result.Name === 'line' ? result.Score > 0.92 : result.Score > 0.85)) { switch (result.Name) { case Gestures.Line: diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index f06f3efe0..3d0216625 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -28,7 +28,6 @@ export interface MarqueeAnnotatorProps { marqueeContainer: HTMLDivElement; docView: () => DocumentView; savedAnnotations: () => ObservableMap; - savedTapes: () => ObservableMap; selectionText: () => string; annotationLayer: HTMLDivElement; addDocument: (doc: Doc) => boolean; @@ -75,7 +74,6 @@ export class MarqueeAnnotator extends ObservableReactComponent): Opt => { - // const savedTapeMap = savedTapes?.values() && Array.from(savedTapes?.values()).length ? savedTapes : this.props.savedTapes(); - // if (savedTapeMap.size === 0) return undefined; - // const tapes = Array.from(savedTapeMap.values())[0]; - // const doc = this.props.Document; - // const scale = (this.props.annotationLayerScaling?.() || 1) * NumCast(doc._freeform_scale, 1); - // if (tapes.length && (tapes[0] as any).marqueeing) { - // const anno = tapes[0]; - // const containerOffset = this.props.containerOffset?.() || [0, 0]; - // const tape = Docs.Create.FreeformDocument([], { - // onClick: isLinkButton ? FollowLinkScript() : undefined, - // backgroundColor: color, - // annotationOn: this.props.Document, - // title: 'Tape on ' + this.props.Document.title, - // }); - // tape.x = NumCast(doc.freeform_panX_min) + (parseInt(anno.style.left || '0') - containerOffset[0]) / scale; - // tape.y = NumCast(doc.freeform_panY_min) + (parseInt(anno.style.top || '0') - containerOffset[1]) / scale; - // tape._height = parseInt(anno.style.height || '0') / scale; - // tape._width = parseInt(anno.style.width || '0') / scale; - // anno.remove(); - // savedTapeMap.clear(); - // return tape; - // } - - // const textRegionAnno = Docs.Create.ConfigDocument({ - // annotationOn: this.props.Document, - // text: this.props.selectionText() as any, // text want an RTFfield, but strings are acceptable, too. - // text_html: this.props.selectionText() as any, - // backgroundColor: 'transparent', - // presentation_duration: 2100, - // presentation_transition: 500, - // presentation_zoomText: true, - // title: '>' + this.props.Document.title, - // }); - // const textRegionAnnoProto = textRegionAnno[DocData]; - // let minX = Number.MAX_VALUE; - // let maxX = -Number.MAX_VALUE; - // let minY = Number.MAX_VALUE; - // let maxY = -Number.MIN_VALUE; - // const annoRects: string[] = []; - // savedAnnoMap.forEach((value: HTMLDivElement[]) => - // value.forEach(anno => { - // const x = parseInt(anno.style.left ?? '0'); - // const y = parseInt(anno.style.top ?? '0'); - // const height = parseInt(anno.style.height ?? '0'); - // const width = parseInt(anno.style.width ?? '0'); - // annoRects.push(`${x}:${y}:${width}:${height}`); - // anno.remove(); - // minY = Math.min(NumCast(y), minY); - // minX = Math.min(NumCast(x), minX); - // maxY = Math.max(NumCast(y) + NumCast(height), maxY); - // maxX = Math.max(NumCast(x) + NumCast(width), maxX); - // }) - // ); - - // textRegionAnnoProto.y = Math.max(minY, 0); - // textRegionAnnoProto.x = Math.max(minX, 0); - // textRegionAnnoProto.height = Math.max(maxY, 0) - Math.max(minY, 0); - // textRegionAnnoProto.width = Math.max(maxX, 0) - Math.max(minX, 0); - // textRegionAnnoProto.backgroundColor = color; - // // mainAnnoDocProto.text = this._selectionText; - // textRegionAnnoProto.text_inlineAnnotations = new List(annoRects); - // textRegionAnnoProto.opacity = 0; - // textRegionAnnoProto.layout_unrendered = true; - // savedAnnoMap.clear(); - // return textRegionAnno; - // }; - - @undoBatch - makeTapeDocument = (color: string, isLinkButton?: boolean, savedTapes?: ObservableMap): Opt => { - // const savedAnnoMap = savedTapes?.values() && Array.from(savedTapes?.values()).length ? savedTapes : this.props.savedTapes(); - // if (savedAnnoMap.size === 0) return undefined; - // const savedAnnos = Array.from(savedAnnoMap.values())[0]; - const doc = this.props.Document; - const scale = (this.props.annotationLayerScaling?.() || 1) * NumCast(doc._freeform_scale, 1); - const marqueeAnno = Docs.Create.FreeformDocument([], { - onClick: isLinkButton ? FollowLinkScript() : undefined, - backgroundColor: color, - annotationOn: this.props.Document, - title: 'Annotation on ' + this.props.Document.title, - }); - marqueeAnno.x = NumCast(doc.freeform_panX_min) / scale; - marqueeAnno.y = NumCast(doc.freeform_panY_min) / scale; - marqueeAnno._height = parseInt('100') / scale; - marqueeAnno._width = parseInt('100') / scale; - return marqueeAnno; - // } - - // const textRegionAnno = Docs.Create.ConfigDocument({ - // annotationOn: this.props.Document, - // text: this.props.selectionText() as any, // text want an RTFfield, but strings are acceptable, too. - // text_html: this.props.selectionText() as any, - // backgroundColor: 'transparent', - // presentation_duration: 2100, - // presentation_transition: 500, - // presentation_zoomText: true, - // title: '>' + this.props.Document.title, - // }); - // const textRegionAnnoProto = textRegionAnno[DocData]; - // let minX = Number.MAX_VALUE; - // let maxX = -Number.MAX_VALUE; - // let minY = Number.MAX_VALUE; - // let maxY = -Number.MIN_VALUE; - // const annoRects: string[] = []; - // savedAnnoMap.forEach((value: HTMLDivElement[]) => - // value.forEach(anno => { - // const x = parseInt(anno.style.left ?? '0'); - // const y = parseInt(anno.style.top ?? '0'); - // const height = parseInt(anno.style.height ?? '0'); - // const width = parseInt(anno.style.width ?? '0'); - // annoRects.push(`${x}:${y}:${width}:${height}`); - // anno.remove(); - // minY = Math.min(NumCast(y), minY); - // minX = Math.min(NumCast(x), minX); - // maxY = Math.max(NumCast(y) + NumCast(height), maxY); - // maxX = Math.max(NumCast(x) + NumCast(width), maxX); - // }) - // ); - - // textRegionAnnoProto.y = Math.max(minY, 0); - // textRegionAnnoProto.x = Math.max(minX, 0); - // textRegionAnnoProto.height = Math.max(maxY, 0) - Math.max(minY, 0); - // textRegionAnnoProto.width = Math.max(maxX, 0) - Math.max(minX, 0); - // textRegionAnnoProto.backgroundColor = color; - // // mainAnnoDocProto.text = this._selectionText; - // textRegionAnnoProto.text_inlineAnnotations = new List(annoRects); - // textRegionAnnoProto.opacity = 0; - // textRegionAnnoProto.layout_unrendered = true; - // savedAnnoMap.clear(); - // return textRegionAnno; - }; - @action highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap, addAsAnnotation?: boolean) => { // creates annotation documents for current highlights @@ -272,15 +137,6 @@ export class MarqueeAnnotator extends ObservableReactComponent, addAsAnnotation?: boolean) => { - // creates annotation documents for current highlights - const effectiveAcl = GetEffectiveAcl(this.props.Document[DocData]); - const tape = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeTapeDocument(color, isLinkButton, savedTapes); - addAsAnnotation && tape && this.props.addDocument(tape); - return tape as Doc; - }; - public static previewNewAnnotation = action((savedAnnotations: ObservableMap, annotationLayer: HTMLDivElement, div: HTMLDivElement, page: number) => { div.style.backgroundColor = '#ACCEF7'; div.style.opacity = '0.5'; @@ -327,7 +183,6 @@ export class MarqueeAnnotator extends ObservableReactComponent this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true)), 'make sidebar annotation'); AnchorMenu.Instance.OnAudio = unimplementedFunction; AnchorMenu.Instance.Highlight = (color: string) => this.highlight(color, false, undefined, true); - AnchorMenu.Instance.Tape = (color: string) => this.tape(color, false, undefined, true); AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap /* , addAsAnnotation?: boolean */) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, true); AnchorMenu.Instance.onMakeAnchor = () => AnchorMenu.Instance.GetAnchor(undefined, true); diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss index aa825a6e9..a5e60b831 100644 --- a/src/client/views/PropertiesView.scss +++ b/src/client/views/PropertiesView.scss @@ -642,5 +642,5 @@ .smooth, .color, .smooth-slider { - margin-top: 3px; + margin-top: 7px; } diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index ac2625f32..c7b0a2ba5 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -802,7 +802,7 @@ export class PropertiesView extends ObservableReactComponent + {!targetDoc.layout_isSvg && ( +
+ } + iconPlacement="left" + align="flex-start" + fillWidth + toggleType={ToggleType.BUTTON} + onClick={undoable(() => { + SmartDrawHandler.Instance.colorWithGPT(targetDoc); + }, 'smoothStrokes')} + /> +
+ )}
- {!targetDoc.layout_isSvg && ( -
- } - iconPlacement="left" - align="flex-start" - fillWidth - toggleType={ToggleType.BUTTON} - onClick={undoable(() => { - SmartDrawHandler.Instance.colorWithGPT(targetDoc); - }, 'smoothStrokes')} - /> -
- )}
); } @@ -1026,9 +1026,15 @@ export class PropertiesView extends ObservableReactComponent { + doc[DocData].stroke_markerScale = Number(value); + }); + } this.selectedDoc && (this.selectedDoc[DocData].stroke_markerScale = Number(value)); } - @computed get smoothAmt() { return Number(this.getField('stroke_smoothAmount') || '1'); } // prettier-ignore + @computed get smoothAmt() { return Number(this.getField('stroke_smoothAmount') || '10'); } // prettier-ignore set smoothAmt(value) { this.selectedDoc && (this.selectedDoc[DocData].stroke_smoothAmount = Number(value)); } @@ -1039,9 +1045,8 @@ export class PropertiesView extends ObservableReactComponent { doc[DocData].stroke_startMarker = value; }); - } else { - this.selectedDoc && (this.selectedDoc[DocData].stroke_startMarker = value); } + this.selectedDoc && (this.selectedDoc[DocData].stroke_startMarker = value); } @computed get markTail() { return this.getField('stroke_endMarker') || ''; } // prettier-ignore set markTail(value) { @@ -1050,9 +1055,8 @@ export class PropertiesView extends ObservableReactComponent { doc[DocData].stroke_endMarker = value; }); - } else { - this.selectedDoc && (this.selectedDoc[DocData].stroke_endMarker = value); } + this.selectedDoc && (this.selectedDoc[DocData].stroke_endMarker = value); } regInput = (key: string, value: any, setter: (val: string) => {}) => ( @@ -1304,6 +1308,10 @@ export class PropertiesView extends ObservableReactComponent { const childDocs: Doc[] = DocListCast(selectedDoc[DocData].data); for (var i = 0; i < childDocs.length; i++) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8575807b3..8dc5f03b0 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -599,6 +599,15 @@ export class CollectionFreeFormView extends CollectionSubView { e.stopImmediatePropagation(); const currPoint = { X: e.clientX, Y: e.clientY }; @@ -646,11 +655,6 @@ export class CollectionFreeFormView extends CollectionSubView { this.erase(e, delta); @@ -665,42 +669,6 @@ export class CollectionFreeFormView extends CollectionSubView { - // const currPoint = { X: e.clientX, Y: e.clientY }; - // this._eraserPts.push([currPoint.X, currPoint.Y]); - // this._eraserPts = this._eraserPts.slice(Math.max(0, this._eraserPts.length - 5)); - // const strokeMap: Map = this.getRadiusEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint); - - // strokeMap.forEach((intersects, stroke) => { - // if (!this._deleteList.includes(stroke)) { - // this._deleteList.push(stroke); - // SetActiveInkWidth(StrCast(stroke.Document.stroke_width?.toString()) || '1'); - // SetActiveInkColor(StrCast(stroke.Document.color?.toString()) || 'black'); - // const segments = this.radiusErase(stroke, intersects.sort()); - // segments?.forEach(segment => - // this.forceStrokeGesture( - // e, - // Gestures.Stroke, - // segment.reduce((data, curve) => [...data, ...curve.points.map(p => stroke.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[]) - // ) - // ); - // } - // stroke.layoutDoc.opacity = 0; - // stroke.layoutDoc.dontIntersect = true; - // }); - // return false; - // }; - forceStrokeGesture = (e: PointerEvent, gesture: Gestures, points: InkData, text?: any) => { this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, InkField.getBounds(points), text)); }; @@ -1240,6 +1208,9 @@ export class CollectionFreeFormView extends CollectionSubView { const bounds = InkField.getBounds(points); const B = transformedBounds || this.screenToFreeformContentsXf.transformBounds(bounds.left, bounds.top, bounds.width, bounds.height); @@ -1273,6 +1244,9 @@ export class CollectionFreeFormView extends CollectionSubView { this._drawing = []; @@ -1304,6 +1278,9 @@ export class CollectionFreeFormView extends CollectionSubView { this._batch = UndoManager.StartBatch('regenerateDrawing'); if (doc) { @@ -1317,6 +1294,9 @@ export class CollectionFreeFormView extends CollectionSubView { const docData = doc[DocData]; docData.title = opts.text.match(/^(.*?)~~~.*$/)?.[1] || opts.text; @@ -1926,8 +1906,6 @@ export class CollectionFreeFormView extends CollectionSubView { public pinWithView: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; public classifyImages: (e: React.MouseEvent | undefined) => void = unimplementedFunction; public groupImages: () => void = unimplementedFunction; - public smoothStrokes: (docs?: Doc[]) => void = unimplementedFunction; public isShown = () => this._opacity > 0; constructor(props: any) { super(props); @@ -42,7 +41,6 @@ export class MarqueeOptionsMenu extends AntimodeMenu { } color={this.userColor} /> } color={this.userColor} /> } color={this.userColor} /> - this.smoothStrokes} icon={} color={this.userColor} /> ); return this.getElement(buttons); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 92c0da983..bc1dfed92 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -284,7 +284,6 @@ export class MarqueeView extends ObservableReactComponent { - docs && docs.length > 0 ? (this._selectedDocs = docs) : (this._selectedDocs = this.marqueeSelect(false, DocumentType.INK)); - if (this._selectedDocs.length == 0) return; - - this._selectedDocs.forEach(stroke => { - const docView = DocumentView.getDocumentView(stroke); - const inkStroke = docView?.ComponentView as InkingStroke; - const { inkData } = inkStroke.inkScaledData(); - - const result = inkData.length > 2 && GestureUtils.GestureRecognizer.Recognize([inkData]); - console.log(result); - let polygonPoints: { X: number; Y: number }[] | undefined = undefined; - if (result && (result.Name === 'line' ? result.Score > 0.9 : result.Score > 0.8)) { - switch (result.Name) { - case Gestures.Line: - case Gestures.Triangle: - case Gestures.Rectangle: - case Gestures.Circle: - GestureOverlay.makeBezierPolygon(inkData, result.Name, true); - break; - default: - } - } else { - const distances: number[] = []; - for (var i = 0; i < inkData.length - 3; i += 4) { - distances.push(Math.sqrt((inkData[i].X - inkData[i + 3].X) ** 2 + (inkData[i].Y - inkData[i + 3].Y) ** 2)); - } - const avgDist = (NumCast(stroke.width) + NumCast(stroke.height)) / 2; - // const avgDist = distances.reduce((a, b) => a + b) / distances.length; - if (Math.sqrt((inkData.lastElement().X - inkData[0].X) ** 2 + (inkData.lastElement().Y - inkData[0].Y) ** 2) < avgDist) { - inkData.pop(); - inkData.push({ X: inkData[0].X, Y: inkData[0].Y }); - } - // const editedPoints: InkData = []; - // const toDelete: number[] = []; - - // distances.forEach((dist, i) => { - // if (dist < avgDist / 3) { - // toDelete.unshift(i * 4); - // } - // }); - // toDelete.forEach(pt => { - // InkStrokeProperties.Instance._currentPoint = pt; - // docView && InkStrokeProperties.Instance.deletePoints(docView, false); - // }); - - // for (var i = 0; i < distances.length; i++) { - // if (distances[i] > avgDist / 3) { - // editedPoints.push(...inkData.slice(i * 4, i * 4 + 4)); - // } else { - // if (i !== distances.length) { - // editedPoints.push(...inkData.slice(i * 4, i * 4 + 2)); - // editedPoints.push(...inkData.slice(i * 4 + 6, i * 4 + 8)); - // i++; - // } - // } - // } - // inkData.length = 0; - // inkData.push(...editedPoints); - } - }); - }); - @undoBatch syntaxHighlight = action((e: KeyboardEvent | React.PointerEvent | undefined) => { const selected = this.marqueeSelect(false); diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index ea574493a..7719f2f7c 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -57,7 +57,6 @@ export class AnchorMenu extends AntimodeMenu { public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public Highlight: (color: string) => Opt = (/* color: string */) => undefined; - public Tape: (color: string) => Opt = (/* color: string */) => undefined; public GetAnchor: (savedAnnotations: Opt>, addAsAnnotation: boolean) => Opt = emptyFunction; public Delete: () => void = unimplementedFunction; public PinToPres: () => void = unimplementedFunction; @@ -144,6 +143,9 @@ export class AnchorMenu extends AntimodeMenu { this.addToCollection?.(newCol); }; + /** + * Creates a GPT drawing based on selected text. + */ gptDraw = async (e: React.PointerEvent) => { try { SmartDrawHandler.Instance.AddDrawing = this.createDrawingAnnotation; @@ -155,6 +157,9 @@ export class AnchorMenu extends AntimodeMenu { } }; + /** + * Defines how a GPT drawing should be added to the current document. + */ @undoBatch createDrawingAnnotation = action((drawing: Doc, opts: DrawingOptions, gptRes: string) => { this.AddDrawingAnnotation(drawing); @@ -203,12 +208,6 @@ export class AnchorMenu extends AntimodeMenu { AnchorMenu.Instance.fadeOut(true); }; - @action - tapeClicked = () => { - this.Tape(this.highlightColor); - // AnchorMenu.Instance.fadeOut(true); - }; - @computed get highlighter() { return ( @@ -219,13 +218,6 @@ export class AnchorMenu extends AntimodeMenu { colorPicker={this.highlightColor} color={SettingsManager.userColor} /> - } - onClick={this.tapeClicked} - colorPicker={this.highlightColor} - color={SettingsManager.userColor} - /> ); diff --git a/src/client/views/smartdraw/AnnotationPalette.tsx b/src/client/views/smartdraw/AnnotationPalette.tsx index 7e4d46204..7f00fa2f2 100644 --- a/src/client/views/smartdraw/AnnotationPalette.tsx +++ b/src/client/views/smartdraw/AnnotationPalette.tsx @@ -107,6 +107,10 @@ export class AnnotationPalette extends ObservableReactComponent { if (!doc.savedAsAnno) { const clone = await Doc.MakeClone(doc); @@ -129,6 +133,10 @@ export class AnnotationPalette extends ObservableReactComponent { this._isLoading = true; @@ -159,6 +167,11 @@ export class AnnotationPalette extends ObservableReactComponent { const cIndex: number = this._props.Document.carousel_index as number; const focusedDrawing = DocListCast(this._props.Document.data)[cIndex]; diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index 52df598ee..4ec787e07 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -208,6 +208,9 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> { this._canInteract = true; }; + /** + * Calls GPT API to create a drawing based on user input + */ @action drawWithGPT = async (startPt: { X: number; Y: number }, input: string, complexity: number, size: number, autoColor: boolean) => { if (input === '') return; @@ -226,6 +229,9 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> { return strokeData; }; + /** + * Regenerates drawings with the option to add a specific regenerate prompt/request. + */ @action regenerate = async (lastInput?: DrawingOptions, lastResponse?: string, regenInput?: string) => { if (lastInput) this._lastInput = lastInput; @@ -256,6 +262,9 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> { } }; + /** + * Parses the svg code that GPT returns into Bezier curves. + */ @action parseSvg = async (res: string, startPoint: { X: number; Y: number }, regenerate: boolean, autoColor: boolean) => { const svg = res.match(/]*>([\s\S]*?)<\/svg>/g); @@ -278,6 +287,9 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> { } }; + /** + * Sends request to GPT API to recolor a selected ink document or group of ink documents. + */ colorWithGPT = async (drawing: Doc) => { const img = await this.getIcon(drawing); const { href } = (img as URLField).url; @@ -310,6 +322,9 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> { } }; + /** + * Function that parses the GPT color response and sets the selected stroke(s) to the new color. + */ @undoBatch colorStrokes = (res: string, drawing: Doc) => { const colorList = res.match(/\{.*?\}/g); @@ -320,13 +335,14 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> { strokes[index][DocData].color = strokeAndFill[0]; const inkStroke = DocumentView.getDocumentView(strokes[index])?.ComponentView as InkingStroke; const { inkData } = inkStroke.inkScaledData(); - if (InkingStroke.IsClosed(inkData)) { - strokes[index][DocData].fillColor = strokeAndFill[1]; - } + InkingStroke.IsClosed(inkData) ? (strokes[index][DocData].fillColor = strokeAndFill[1]) : (strokes[index][DocData].fillColor = undefined); } }); }; + /** + * Gets an image snapshot of a doc. In this class, it's used to snapshot a selected ink stroke/group to use for GPT color. + */ async getIcon(doc: Doc) { const docView = DocumentView.getDocumentView(doc); console.log(doc); -- cgit v1.2.3-70-g09d2 From 0ac79ba6a7ab19b4aafbc11dac9bab4781d4bd40 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 29 Aug 2024 13:07:53 -0400 Subject: merge cleanup cleanup --- eslint.config.mjs | 67 +++++- src/ClientUtils.ts | 9 +- src/client/cognitive_services/CognitiveServices.ts | 15 +- src/client/util/Scripting.ts | 4 +- src/client/util/SettingsManager.tsx | 6 +- src/client/util/bezierFit.ts | 18 +- src/client/views/DocumentButtonBar.tsx | 10 +- src/client/views/GestureOverlay.tsx | 10 +- src/client/views/InkStrokeProperties.ts | 268 +++++++++++---------- src/client/views/PropertiesButtons.tsx | 4 +- .../collections/CollectionStackedTimeline.tsx | 2 +- .../views/collections/CollectionTreeView.tsx | 4 - src/client/views/collections/TreeView.tsx | 6 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 22 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 12 +- src/client/views/pdf/Annotation.tsx | 1 + src/client/views/pdf/PDFViewer.tsx | 4 +- src/client/views/smartdraw/AnnotationPalette.tsx | 20 +- src/client/views/smartdraw/SmartDrawHandler.tsx | 2 +- 20 files changed, 256 insertions(+), 230 deletions(-) (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/eslint.config.mjs b/eslint.config.mjs index 12ad3300a..619966f20 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,14 +1,57 @@ -import globals from "globals"; -import pluginJs from "@eslint/js"; -import tseslint from "typescript-eslint"; -import pluginReact from "eslint-plugin-react"; - +import pluginJs from '@eslint/js'; +import pluginReactConfig from 'eslint-plugin-react/configs/recommended.js'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; export default [ - {files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"]}, - {files: ["**/*.js"], languageOptions: {sourceType: "commonjs"}}, - {languageOptions: { globals: globals.browser }}, - pluginJs.configs.recommended, - ...tseslint.configs.recommended, - pluginReact.configs.flat.recommended, -]; \ No newline at end of file + { + languageOptions: { globals: { ...globals.browser, ...globals.node } }, + }, + pluginJs.configs.recommended, + ...tseslint.configs.recommended, + { + rules: { + 'node/no-missing-import': 0, + 'no-console': 'off', + 'func-names': 'off', + 'no-process-exit': 'off', + 'object-shorthand': 'off', + 'class-methods-use-this': 'off', + 'single-quote': 'off', + 'max-classes-per-file': 0, + + 'react/jsx-filename-extension': [ + 2, + { + extensions: ['.js', '.jsx', '.ts', '.tsx'], + }, + ], + + 'import/prefer-default-export': 'off', + 'no-unused-expressions': 'off', + '@typescript-eslint/no-unused-expressions': 'off', + 'prefer-template': 'off', + 'no-inner-declarations': 'off', + 'no-plusplus': 'off', + 'no-multi-assign': 'off', + 'no-underscore-dangle': 'off', + 'no-nested-ternary': 'off', + 'lines-between-class-members': 'off', + 'no-shadow': 'off', + '@typescript-eslint/no-shadow': 'warn', + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-namespace': 'off', + 'react/destructuring-assignment': 0, + 'prefer-arrow-callback': 'error', + 'no-return-assign': 'error', + 'no-await-in-loop': 'error', + 'no-loop-func': 'error', + 'no-cond-assign': 'error', + 'no-use-before-define': 'error', + 'no-explicit-any': 'error', + 'no-restricted-globals': ['error', 'event'], + }, + }, + pluginReactConfig, +]; diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts index dc52218c5..55801df81 100644 --- a/src/ClientUtils.ts +++ b/src/ClientUtils.ts @@ -82,10 +82,6 @@ export function returnEmptyFilter() { return [] as string[]; } -export function returnEmptyDoclist() { - return [] as any[]; -} - export namespace ClientUtils { export const CLICK_TIME = 300; export const DRAG_THRESHOLD = 4; @@ -449,11 +445,10 @@ export function smoothScrollHorizontal(duration: number, element: HTMLElement | animateScroll(); } -export function addStyleSheet(styleType: string = 'text/css') { +export function addStyleSheet() { const style = document.createElement('style'); - style.type = styleType; const sheets = document.head.appendChild(style); - return (sheets as any).sheet; + return sheets.sheet; } export function addStyleSheetRule(sheet: CSSStyleSheet | null, selector: string, css: string | { [key: string]: string }, selectorPrefix = '.') { const propText = diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index 9f7701c54..3ee61cbfb 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -46,15 +46,14 @@ export enum Confidence { export namespace CognitiveServices { const ExecuteQuery = async (service: Service, manager: APIManager, data: D): Promise => { const apiKey = process.env[service.toUpperCase()]; - if (apiKey) { - console.log(data); + if (!apiKey) { console.log(`No API key found for ${service}: ensure youe root directory has .env file with _CLIENT_${service.toUpperCase()}.`); return undefined; } let results: any; try { - results = await manager.requester('has', manager.converter(data), service).then(json => JSON.parse(json)); + results = await manager.requester(apiKey, manager.converter(data), service).then(json => JSON.parse(json)); } catch (e) { throw e; } @@ -138,14 +137,6 @@ export namespace CognitiveServices { points: points.map(({ X: x, Y: y }) => `${x},${y}`).join(','), language: 'en-US', })); - console.log( - JSON.stringify({ - version: 1, - language: 'en-US', - unit: 'mm', - strokes, - }) - ); return JSON.stringify({ version: 1, language: 'en-US', @@ -345,7 +336,7 @@ export namespace CognitiveServices { 'Ocp-Apim-Subscription-Key': apiKey, }, }; - return request.post(options); + return rp.post(options); }, }; diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index cb314e3f1..c63d3d7cb 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -1,7 +1,7 @@ // export const ts = (window as any).ts; // import * as typescriptlib from '!!raw-loader!../../../node_modules/typescript/lib/lib.d.ts' // import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts' -// import typescriptlib from 'type_decls.d'; +import typescriptlib from 'type_decls.d'; import * as ts from 'typescript'; import { Doc, FieldType } from '../../fields/Doc'; import { RefField } from '../../fields/RefField'; @@ -248,7 +248,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp const funcScript = `(function(${paramString})${reqTypes} { ${body} })`; host.writeFile('file.ts', funcScript); - // if (typecheck) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib); + if (typecheck) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib); const program = ts.createProgram(['file.ts'], {}, host); const testResult = program.emit(); const outputText = host.readFile('file.js'); diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 9e49117a7..9200d68db 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -27,7 +27,7 @@ export enum ColorScheme { } @observer -export class SettingsManager extends React.Component<{}> { +export class SettingsManager extends React.Component { // eslint-disable-next-line no-use-before-define public static Instance: SettingsManager; static _settingsStyle = addStyleSheet(); @@ -85,7 +85,7 @@ export class SettingsManager extends React.Component<{}> { if (this._playgroundMode) { DocServer.Control.makeReadOnly(); addStyleSheetRule(SettingsManager._settingsStyle, 'topbar-inner-container', { background: 'red !important' }); - } else ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Control.makeEditable(); + } else if (ClientUtils.CurrentUserEmail() !== 'guest') DocServer.Control.makeEditable(); }), 'set playgorund mode' ); @@ -121,7 +121,7 @@ export class SettingsManager extends React.Component<{}> { 'change color scheme' ); - constructor(props: {}) { + constructor(props: object) { super(props); makeObservable(this); SettingsManager.Instance = this; diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts index 693676bc3..4c7f4a0ba 100644 --- a/src/client/util/bezierFit.ts +++ b/src/client/util/bezierFit.ts @@ -2,7 +2,6 @@ /* eslint-disable prefer-destructuring */ /* eslint-disable no-param-reassign */ /* eslint-disable camelcase */ -import e from 'cors'; import { Point } from '../../pen-gestures/ndollar'; export enum SVGType { @@ -628,7 +627,7 @@ export function GenerateControlPoints(coordinates: Point[], alpha = 0.1) { export function SVGToBezier(name: SVGType, attributes: any): Point[] { switch (name) { - case 'line': + case 'line': { const x1 = parseInt(attributes.x1); const x2 = parseInt(attributes.x2); const y1 = parseInt(attributes.y1); @@ -639,8 +638,9 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] { { X: x2, Y: y2 }, { X: x2, Y: y2 }, ]; + } case 'circle': - case 'ellipse': + case 'ellipse': { const c = 0.551915024494; const centerX = parseInt(attributes.cx); const centerY = parseInt(attributes.cy); @@ -664,7 +664,8 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] { { X: centerX - c * radiusX, Y: centerY + radiusY }, { X: centerX, Y: centerY + radiusY }, ]; - case 'rect': + } + case 'rect': { const x = parseInt(attributes.x); const y = parseInt(attributes.y); const width = parseInt(attributes.width); @@ -687,14 +688,15 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] { { X: x, Y: y }, { X: x, Y: y }, ]; - case 'path': + } + case 'path': { const coordList: Point[] = []; const startPt = attributes.d.match(/M(-?\d+\.?\d*),(-?\d+\.?\d*)/); coordList.push({ X: parseInt(startPt[1]), Y: parseInt(startPt[2]) }); const matches: RegExpMatchArray[] = Array.from( attributes.d.matchAll(/Q(-?\d+\.?\d*),(-?\d+\.?\d*) (-?\d+\.?\d*),(-?\d+\.?\d*)|C(-?\d+\.?\d*),(-?\d+\.?\d*) (-?\d+\.?\d*),(-?\d+\.?\d*) (-?\d+\.?\d*),(-?\d+\.?\d*)|L(-?\d+\.?\d*),(-?\d+\.?\d*)/g) ); - let lastPt: Point; + let lastPt: Point = { X: 0, Y: 0 }; matches.forEach(match => { if (match[0].startsWith('Q')) { coordList.push({ X: parseInt(match[1]), Y: parseInt(match[2]) }); @@ -725,7 +727,8 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] { coordList.pop(); } return coordList; - case 'polygon': + } + case 'polygon': { const coords: RegExpMatchArray[] = Array.from(attributes.points.matchAll(/(-?\d+\.?\d*),(-?\d+\.?\d*)/g)); let list: Point[] = []; coords.forEach(coord => { @@ -737,6 +740,7 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] { const firstPts = list.splice(0, 2); list = list.concat(firstPts); return list; + } default: return []; } diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 8aa4c2093..096f058ad 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -9,7 +9,7 @@ import * as React from 'react'; import { FaEdit } from 'react-icons/fa'; import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; -import { Doc, DocListCast } from '../../fields/Doc'; +import { Doc } from '../../fields/Doc'; import { Cast, DocCast } from '../../fields/Types'; import { DocUtils, IsFollowLinkScript } from '../documents/DocUtils'; import { CalendarManager } from '../util/CalendarManager'; @@ -17,7 +17,7 @@ import { DictationManager } from '../util/DictationManager'; import { DragManager } from '../util/DragManager'; import { dropActionType } from '../util/DropActionTypes'; import { SharingManager } from '../util/SharingManager'; -import { UndoManager, undoable, undoBatch } from '../util/UndoManager'; +import { UndoManager, undoable } from '../util/UndoManager'; import './DocumentButtonBar.scss'; import { ObservableReactComponent } from './ObservableReactComponent'; import { PinProps } from './PinFuncs'; @@ -29,7 +29,6 @@ import { DocumentView } from './nodes/DocumentView'; import { OpenWhere } from './nodes/OpenWhere'; import { DashFieldView } from './nodes/formattedText/DashFieldView'; import { AnnotationPalette } from './smartdraw/AnnotationPalette'; -import { DocData } from '../../fields/DocSymbols'; @observer export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (DocumentView | undefined)[]; stack?: unknown }> { @@ -240,10 +239,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( ); } - @undoBatch - saveAnno = action(async (targetDoc: Doc) => { - await AnnotationPalette.addToPalette(targetDoc); - }); + saveAnno = undoable(async (targetDoc: Doc) => await AnnotationPalette.addToPalette(targetDoc), 'save to palette'); @computed get saveAnnoButton() { diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 63b472faf..befd19f6e 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -129,7 +129,6 @@ export class GestureOverlay extends ObservableReactComponent { - console.log('pointer up'); DocumentView.DownDocView = undefined; if (this._points.length > 1) { const B = this.svgBounds; @@ -145,9 +144,7 @@ export class GestureOverlay extends ObservableReactComponent 2 && GestureUtils.GestureRecognizer.Recognize([points]); - console.log(points); let actionPerformed = false; - console.log(result); if (Doc.UserDoc().recognizeGestures && result && result.Score > 0.7) { switch (result.Name) { case Gestures.Line: @@ -156,15 +153,16 @@ export class GestureOverlay extends ObservableReactComponent { + addPoints = undoable((inkView: DocumentView, t: number, i: number, controls: { X: number; Y: number }[]) => { this.applyFunction(inkView, (view: DocumentView /* , ink: InkData */) => { const doc = view.Document; const array = [controls[i], controls[i + 1], controls[i + 2], controls[i + 3]]; @@ -109,7 +108,7 @@ export class InkStrokeProperties { return controls; }); - }; + }, 'add ink points'); /** * Scales a handle point of a control point that is adjacent to a newly added one. @@ -164,46 +163,48 @@ export class InkStrokeProperties { /** * Deletes the current control point of the selected ink instance. */ - @undoBatch - deletePoints = (inkView: DocumentView, preserve: boolean) => - this.applyFunction( - inkView, - (view: DocumentView, ink: InkData) => { - const doc = view.Document; - const newPoints = ink.slice(); - const brokenIndices = NumListCast(doc.brokenInkIndices); - if (preserve || this._currentPoint === 0 || this._currentPoint === ink.length - 1 || brokenIndices.includes(this._currentPoint)) { - newPoints.splice(this._currentPoint === 0 ? 0 : this._currentPoint === ink.length - 1 ? this._currentPoint - 3 : this._currentPoint - 2, 4); - } else { - const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4; - const splicedPoints = ink.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8)); - const samples: Point[] = []; - let startDir = { x: 0, y: 0 }; - let endDir = { x: 0, y: 0 }; - for (let i = 0; i < splicedPoints.length / 4; i++) { - const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y }))); - if (i === 0) startDir = bez.derivative(0); - if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1); - for (let t = 0; t < (i === splicedPoints.length / 4 - 1 ? 1 + 1e-7 : 1); t += 0.05) { - const pt = bez.compute(t); - samples.push(new Point(pt.x, pt.y)); - } - } - const { finalCtrls, error } = FitOneCurve(samples, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y }); - if (error < 100) { - newPoints.splice(this._currentPoint - 4, 8, ...finalCtrls); + deletePoints = undoable( + (inkView: DocumentView, preserve: boolean) => + this.applyFunction( + inkView, + (view: DocumentView, ink: InkData) => { + const doc = view.Document; + const newPoints = ink.slice(); + const brokenIndices = NumListCast(doc.brokenInkIndices); + if (preserve || this._currentPoint === 0 || this._currentPoint === ink.length - 1 || brokenIndices.includes(this._currentPoint)) { + newPoints.splice(this._currentPoint === 0 ? 0 : this._currentPoint === ink.length - 1 ? this._currentPoint - 3 : this._currentPoint - 2, 4); } else { - newPoints.splice(this._currentPoint - 2, 4); + const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4; + const splicedPoints = ink.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8)); + const samples: Point[] = []; + let startDir = { x: 0, y: 0 }; + let endDir = { x: 0, y: 0 }; + for (let i = 0; i < splicedPoints.length / 4; i++) { + const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y }))); + if (i === 0) startDir = bez.derivative(0); + if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1); + for (let t = 0; t < (i === splicedPoints.length / 4 - 1 ? 1 + 1e-7 : 1); t += 0.05) { + const pt = bez.compute(t); + samples.push(new Point(pt.x, pt.y)); + } + } + const { finalCtrls, error } = FitOneCurve(samples, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y }); + if (error < 100) { + newPoints.splice(this._currentPoint - 4, 8, ...finalCtrls); + } else { + newPoints.splice(this._currentPoint - 2, 4); + } } - } - doc.brokenInkIndices = new List(brokenIndices.map(control => (control >= this._currentPoint ? control - 4 : control))); - runInAction(() => { - this._currentPoint = -1; - }); - return newPoints.length < 4 ? undefined : newPoints; - }, - true - ); + doc.brokenInkIndices = new List(brokenIndices.map(control => (control >= this._currentPoint ? control - 4 : control))); + runInAction(() => { + this._currentPoint = -1; + }); + return newPoints.length < 4 ? undefined : newPoints; + }, + true + ), + 'delete ink points' + ); /** * Rotates ink stroke(s) about a point @@ -211,8 +212,7 @@ export class InkStrokeProperties { * @param angle The angle at which to rotate the ink in radians. * @param scrpt The center point of the rotation in screen coordinates */ - @undoBatch - rotateInk = (inkStrokes: DocumentView[], angle: number, scrpt: PointData) => { + rotateInk = undoable((inkStrokes: DocumentView[], angle: number, scrpt: PointData) => { this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData, xScale: number, yScale: number /* , inkStrokeWidth: number */) => { const inkCenterPt = view.ComponentView?.ptFromScreen?.(scrpt); return !inkCenterPt @@ -224,7 +224,7 @@ export class InkStrokeProperties { return { X: newX + inkCenterPt.X, Y: newY + inkCenterPt.Y }; }); }); - }; + }, 'rotate ink'); /** * Rotates ink stroke(s) about a point @@ -232,8 +232,7 @@ export class InkStrokeProperties { * @param angle The angle at which to rotate the ink in radians. * @param scrpt The center point of the rotation in screen coordinates */ - @undoBatch - stretchInk = (inkStrokes: DocumentView[], scaling: number, scrpt: PointData, scrVec: PointData, scaleUniformly: boolean) => { + stretchInk = undoable((inkStrokes: DocumentView[], scaling: number, scrpt: PointData, scrVec: PointData, scaleUniformly: boolean) => { this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData) => { const ptFromScreen = view.ComponentView?.ptFromScreen; const ptToScreen = view.ComponentView?.ptToScreen; @@ -247,77 +246,79 @@ export class InkStrokeProperties { return ptFromScreen(newscrpt); }); }); - }; + }, 'stretch ink'); /** * Handles the movement/scaling of a control point. */ - @undoBatch - moveControlPtHandle = (inkView: DocumentView, deltaX: number, deltaY: number, controlIndex: number, origInk?: InkData) => - inkView && - this.applyFunction(inkView, (view: DocumentView, ink: InkData) => { - const order = controlIndex % 4; - const closed = InkingStroke.IsClosed(ink); - const brokenIndices = Cast(inkView.Document.brokenInkIndices, listSpec('number'), []); - if (origInk && this._currentPoint > 0 && this._currentPoint < ink.length - 1 && brokenIndices.findIndex(value => value === controlIndex) === -1) { - const cptBefore = ink[controlIndex]; - const cpt = { X: cptBefore.X + deltaX, Y: cptBefore.Y + deltaY }; - const newink = origInk.slice(); - const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4; - const splicedPoints = origInk.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8)); - const { nearestT, nearestSeg } = InkStrokeProperties.nearestPtToStroke(splicedPoints, cpt); - if ((nearestSeg === 0 && nearestT < 1e-1) || (nearestSeg === 4 && 1 - nearestT < 1e-1) || nearestSeg < 0) return ink.slice(); - const samplesLeft: Point[] = []; - const samplesRight: Point[] = []; - let startDir = { x: 0, y: 0 }; - let endDir = { x: 0, y: 0 }; - for (let i = 0; i < nearestSeg / 4 + 1; i++) { - const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y }))); - if (i === 0) startDir = bez.derivative(_.isEqual(bez.derivative(0), { x: 0, y: 0, t: 0 }) ? 1e-8 : 0); - if (i === nearestSeg / 4) endDir = bez.derivative(nearestT); - for (let t = 0; t < (i === nearestSeg / 4 ? nearestT + 0.05 : 1); t += 0.05) { - const pt = bez.compute(i !== nearestSeg / 4 ? t : Math.min(nearestT, t)); - samplesLeft.push(new Point(pt.x, pt.y)); + moveControlPtHandle = undoable( + (inkView: DocumentView, deltaX: number, deltaY: number, controlIndex: number, origInk?: InkData) => + inkView && + this.applyFunction(inkView, (view: DocumentView, ink: InkData) => { + const order = controlIndex % 4; + const closed = InkingStroke.IsClosed(ink); + const brokenIndices = Cast(inkView.Document.brokenInkIndices, listSpec('number'), []); + if (origInk && this._currentPoint > 0 && this._currentPoint < ink.length - 1 && brokenIndices.findIndex(value => value === controlIndex) === -1) { + const cptBefore = ink[controlIndex]; + const cpt = { X: cptBefore.X + deltaX, Y: cptBefore.Y + deltaY }; + const newink = origInk.slice(); + const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4; + const splicedPoints = origInk.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8)); + const { nearestT, nearestSeg } = InkStrokeProperties.nearestPtToStroke(splicedPoints, cpt); + if ((nearestSeg === 0 && nearestT < 1e-1) || (nearestSeg === 4 && 1 - nearestT < 1e-1) || nearestSeg < 0) return ink.slice(); + const samplesLeft: Point[] = []; + const samplesRight: Point[] = []; + let startDir = { x: 0, y: 0 }; + let endDir = { x: 0, y: 0 }; + for (let i = 0; i < nearestSeg / 4 + 1; i++) { + const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y }))); + if (i === 0) startDir = bez.derivative(_.isEqual(bez.derivative(0), { x: 0, y: 0, t: 0 }) ? 1e-8 : 0); + if (i === nearestSeg / 4) endDir = bez.derivative(nearestT); + for (let t = 0; t < (i === nearestSeg / 4 ? nearestT + 0.05 : 1); t += 0.05) { + const pt = bez.compute(i !== nearestSeg / 4 ? t : Math.min(nearestT, t)); + samplesLeft.push(new Point(pt.x, pt.y)); + } } - } - let { finalCtrls } = FitOneCurve(samplesLeft, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y }); - for (let i = nearestSeg / 4; i < splicedPoints.length / 4; i++) { - const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y }))); - if (i === nearestSeg / 4) startDir = bez.derivative(nearestT); - if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(_.isEqual(bez.derivative(1), { x: 0, y: 0, t: 1 }) ? 1 - 1e-8 : 1); - for (let t = i === nearestSeg / 4 ? nearestT : 0; t < (i === nearestSeg / 4 ? 1 + 0.05 + 1e-7 : 1 + 1e-7); t += 0.05) { - const pt = bez.compute(Math.min(1, t)); - samplesRight.push(new Point(pt.x, pt.y)); + let { finalCtrls } = FitOneCurve(samplesLeft, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y }); + for (let i = nearestSeg / 4; i < splicedPoints.length / 4; i++) { + const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y }))); + if (i === nearestSeg / 4) startDir = bez.derivative(nearestT); + if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(_.isEqual(bez.derivative(1), { x: 0, y: 0, t: 1 }) ? 1 - 1e-8 : 1); + for (let t = i === nearestSeg / 4 ? nearestT : 0; t < (i === nearestSeg / 4 ? 1 + 0.05 + 1e-7 : 1 + 1e-7); t += 0.05) { + const pt = bez.compute(Math.min(1, t)); + samplesRight.push(new Point(pt.x, pt.y)); + } } + const { finalCtrls: rightCtrls /* , error: errorRight */ } = FitOneCurve(samplesRight, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y }); + finalCtrls = finalCtrls.concat(rightCtrls); + newink.splice(this._currentPoint - 4, 8, ...finalCtrls); + return newink; } - const { finalCtrls: rightCtrls /* , error: errorRight */ } = FitOneCurve(samplesRight, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y }); - finalCtrls = finalCtrls.concat(rightCtrls); - newink.splice(this._currentPoint - 4, 8, ...finalCtrls); - return newink; - } - return ink.map((pt, i) => { - const leftHandlePoint = order === 0 && i === controlIndex + 1; - const rightHandlePoint = order === 0 && controlIndex !== 0 && i === controlIndex - 2; - if (controlIndex === i || (order === 0 && controlIndex !== 0 && i === controlIndex - 1) || (order === 3 && i === controlIndex - 1)) { - return { X: pt.X + deltaX, Y: pt.Y + deltaY }; - } - if ( - controlIndex === i || - leftHandlePoint || - rightHandlePoint || - (order === 0 && controlIndex !== 0 && i === controlIndex - 1) || - ((order === 0 || order === 3) && (controlIndex === 0 || controlIndex === ink.length - 1) && (i === 1 || i === ink.length - 2) && closed) || - (order === 3 && i === controlIndex - 1) || - (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 1) || - (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 2) || - (ink[0].X === ink[ink.length - 1].X && ink[0].Y === ink[ink.length - 1].Y && (i === 0 || i === ink.length - 1) && (controlIndex === 0 || controlIndex === ink.length - 1)) - ) { - return { X: pt.X + deltaX, Y: pt.Y + deltaY }; - } - return pt; - }); - }); + return ink.map((pt, i) => { + const leftHandlePoint = order === 0 && i === controlIndex + 1; + const rightHandlePoint = order === 0 && controlIndex !== 0 && i === controlIndex - 2; + if (controlIndex === i || (order === 0 && controlIndex !== 0 && i === controlIndex - 1) || (order === 3 && i === controlIndex - 1)) { + return { X: pt.X + deltaX, Y: pt.Y + deltaY }; + } + if ( + controlIndex === i || + leftHandlePoint || + rightHandlePoint || + (order === 0 && controlIndex !== 0 && i === controlIndex - 1) || + ((order === 0 || order === 3) && (controlIndex === 0 || controlIndex === ink.length - 1) && (i === 1 || i === ink.length - 2) && closed) || + (order === 3 && i === controlIndex - 1) || + (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 1) || + (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 2) || + (ink[0].X === ink[ink.length - 1].X && ink[0].Y === ink[ink.length - 1].Y && (i === 0 || i === ink.length - 1) && (controlIndex === 0 || controlIndex === ink.length - 1)) + ) { + return { X: pt.X + deltaX, Y: pt.Y + deltaY }; + } + return pt; + }); + }), + 'move ink ctrl pt' + ); public static nearestPtToStroke(ctrlPoints: { X: number; Y: number }[], refInkSpacePt: { X: number; Y: number }, excludeSegs?: number[]) { let distance = Number.MAX_SAFE_INTEGER; @@ -470,26 +471,28 @@ export class InkStrokeProperties { /** * Handles the movement/scaling of a handle point. */ - @undoBatch - moveTangentHandle = (inkView: DocumentView, deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) => - this.applyFunction(inkView, (view: DocumentView, ink: InkData) => { - const doc = view.Document; - const closed = InkingStroke.IsClosed(ink); - const oldHandlePoint = ink[handleIndex]; - const oppositeHandlePoint = ink[oppositeHandleIndex]; - const controlPoint = ink[controlIndex]; - const newHandlePoint = { X: ink[handleIndex].X - deltaX, Y: ink[handleIndex].Y - deltaY }; - const inkCopy = ink.slice(); - inkCopy[handleIndex] = newHandlePoint; - const brokenIndices = Cast(doc.brokenInkIndices, listSpec('number')); - const equivIndex = closed ? (controlIndex === 0 ? ink.length - 1 : controlIndex === ink.length - 1 ? 0 : -1) : -1; - // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle). - if ((!brokenIndices || (!brokenIndices?.includes(controlIndex) && !brokenIndices?.includes(equivIndex))) && (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { - const angle = InkStrokeProperties.angleChange(oldHandlePoint, newHandlePoint, controlPoint); - inkCopy[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); - } - return inkCopy; - }); + moveTangentHandle = undoable( + (inkView: DocumentView, deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) => + this.applyFunction(inkView, (view: DocumentView, ink: InkData) => { + const doc = view.Document; + const closed = InkingStroke.IsClosed(ink); + const oldHandlePoint = ink[handleIndex]; + const oppositeHandlePoint = ink[oppositeHandleIndex]; + const controlPoint = ink[controlIndex]; + const newHandlePoint = { X: ink[handleIndex].X - deltaX, Y: ink[handleIndex].Y - deltaY }; + const inkCopy = ink.slice(); + inkCopy[handleIndex] = newHandlePoint; + const brokenIndices = Cast(doc.brokenInkIndices, listSpec('number')); + const equivIndex = closed ? (controlIndex === 0 ? ink.length - 1 : controlIndex === ink.length - 1 ? 0 : -1) : -1; + // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle). + if ((!brokenIndices || (!brokenIndices?.includes(controlIndex) && !brokenIndices?.includes(equivIndex))) && (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { + const angle = InkStrokeProperties.angleChange(oldHandlePoint, newHandlePoint, controlPoint); + inkCopy[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); + } + return inkCopy; + }), + 'move ink tangent' + ); /** * Function that "smooths" ink strokes by using the gesture recognizer to detect shapes and @@ -497,8 +500,7 @@ export class InkStrokeProperties { * @param inkDocs * @param tolerance Determines how strong the smooth effect will be */ - @undoBatch - smoothInkStrokes = (inkDocs: Doc[], tolerance: number = 5) => { + smoothInkStrokes = undoable((inkDocs: Doc[], tolerance: number = 5) => { inkDocs.forEach(inkDoc => { const inkView = DocumentView.getDocumentView(inkDoc); const inkStroke = inkView?.ComponentView as InkingStroke; @@ -530,5 +532,5 @@ export class InkStrokeProperties { } } }); - }; + }, 'smooth ink stroke'); } diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index b94041642..f346d4ba8 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -13,7 +13,7 @@ import { MdClosedCaption, MdClosedCaptionDisabled, MdGridOff, MdGridOn, MdSubtit import { RxWidth } from 'react-icons/rx'; import { TbEditCircle, TbEditCircleOff, TbHandOff, TbHandStop, TbHighlight, TbHighlightOff } from 'react-icons/tb'; import { TfiBarChart } from 'react-icons/tfi'; -import { Doc, DocListCast, Opt } from '../../fields/Doc'; +import { Doc, Opt } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; import { ScriptField } from '../../fields/ScriptField'; import { BoolCast, ScriptCast, StrCast } from '../../fields/Types'; @@ -28,8 +28,6 @@ import { Colors } from './global/globalEnums'; import { DocumentView } from './nodes/DocumentView'; import { OpenWhere } from './nodes/OpenWhere'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; -import { MarqueeOptionsMenu } from './collections/collectionFreeForm'; -import { InkStrokeProperties } from './InkStrokeProperties'; @observer export class PropertiesButtons extends React.Component { diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 98ddc61f9..486c826b6 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -847,7 +847,7 @@ class StackedTimelineAnchor extends ObservableReactComponent>() { public static AddTreeFunc = 'addTreeFolder(this.embedContainer)'; private _treedropDisposer?: DragManager.DragDropDisposer; - private _mainEle?: HTMLDivElement; private _titleRef?: HTMLDivElement | HTMLInputElement | null; private _disposers: { [name: string]: IReactionDisposer } = {}; private _isDisposing = false; // notes that instance is in process of being disposed @@ -81,8 +80,6 @@ export class CollectionTreeView extends CollectionSubView this._mainEle; - // these should stay in synch with counterparts in DocComponent.ts ViewBoxAnnotatableComponent @observable _isAnyChildContentActive = false; whenChildContentsActiveChanged = action((isActive: boolean) => { @@ -132,7 +129,6 @@ export class CollectionTreeView extends CollectionSubView { this._treedropDisposer?.(); - this._mainEle = ele; if (ele) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.Document, this.onInternalPreDrop.bind(this)); }; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 847ff5491..015f77ffd 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -472,7 +472,7 @@ export class TreeView extends ObservableReactComponent { const { translateX, translateY, scale } = ClientUtils.GetScreenTransform(ref); return new Transform(-translateX, -translateY, 1).scale(1 / scale); }; - docTransform = () => this.refTransform(this._dref?.ContentRef?.current); + docTransform = () => this.refTransform(this._dref?.ContentDiv); getTransform = () => this.refTransform(this._tref.current); embeddedPanelWidth = () => this._props.panelWidth() / (this.treeView._props.NativeDimScaling?.() || 1); embeddedPanelHeight = () => { @@ -754,7 +754,7 @@ export class TreeView extends ObservableReactComponent { } get onCheckedClick() { - return this.Document.type === DocumentType.COL ? undefined : this._props.onCheckedClick?.() ?? ScriptCast(this.Document.onCheckedClick); + return this.Document.type === DocumentType.COL ? undefined : (this._props.onCheckedClick?.() ?? ScriptCast(this.Document.onCheckedClick)); } @action @@ -779,7 +779,7 @@ export class TreeView extends ObservableReactComponent { TraceMobx(); const iconType = (this.treeView._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':treeOpen' : !this.childDocs.length ? ':empty' : '')) as string) || 'question'; const color = SettingsManager.userColor; - const checked = this.onCheckedClick ? this.Document.treeView_Checked ?? 'unchecked' : undefined; + const checked = this.onCheckedClick ? (this.Document.treeView_Checked ?? 'unchecked') : undefined; return (
- ); + )); }; render() { @@ -994,7 +994,7 @@ export class DocumentViewInternal extends DocComponent() { finished?: (changed: boolean) => void // func called after focusing on target with flag indicating whether anything needed to be done. ) => Promise; public static linkCommonAncestor: (link: Doc) => DocumentView | undefined; - // pin func + /** + * Pins a Doc to the current presentation trail. (see TabDocView for implementation) + */ public static PinDoc: (docIn: Doc | Doc[], pinProps: PinProps) => void; - // gesture + /** + * The DocumentView below the cursor at the start of a gesture (that receives the pointerDown event). Used by GestureOverlay to determine the doc a gesture should apply to. + */ public static DownDocView: DocumentView | undefined; // the first DocView that receives a pointerdown event. used by GestureOverlay to determine the doc a gesture should apply to. public get displayName() { return 'DocumentView(' + (this.Document?.title??"") + ')'; } // prettier-ignore - public ContentRef = React.createRef(); private _htmlOverlayEffect: Opt; private _disposers: { [name: string]: IReactionDisposer } = {}; private _viewTimer: NodeJS.Timeout | undefined; @@ -1468,11 +1471,10 @@ export class DocumentView extends DocComponent() { {!this.Document || !this._props.PanelWidth() ? null : (
(['Audio Tags', 'Text from Others', 'Todo Items', 'Important Items', 'Disagree Items', 'Ignore Items']); - static _highlightStyleSheet: any = addStyleSheet(); - static _bulletStyleSheet: any = addStyleSheet(); - static _userStyleSheet: any = addStyleSheet(); + static _highlightStyleSheet = addStyleSheet(); + static _bulletStyleSheet = addStyleSheet(); + static _userStyleSheet = addStyleSheet(); static _hadSelection: boolean = false; private _oldWheel: HTMLDivElement | null = null; private _selectionHTML: string | undefined; @@ -361,7 +361,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent ) : ( -
setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => DocumentView.SelectView(this.DocumentView?.()!, false), true)}> +
setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => DocumentView.SelectView(this.DocumentView?.(), false), true)}> { outline = () => (this.linkHighlighted ? 'solid 1px lightBlue' : undefined); background = () => (this._props.annoDoc[Highlight] ? 'orange' : StrCast(this._props.annoDoc.backgroundColor)); render() { + const forceRenderHack = [this.background(), this.outline(), this.opacity()]; // forces a re-render when these change -- because RegionAnnotation doesn't do this internally.. return (
{StrListCast(this._props.annoDoc.text_inlineAnnotations) diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 5984905d0..20719442a 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -52,7 +52,7 @@ interface IViewerProps extends FieldViewProps { */ @observer export class PDFViewer extends ObservableReactComponent { - static _annotationStyle: any = addStyleSheet(); + static _annotationStyle = addStyleSheet(); constructor(props: IViewerProps) { super(props); @@ -522,7 +522,7 @@ export class PDFViewer extends ObservableReactComponent { if (doc instanceof Doc && property === StyleProp.PointerEvents) { if (this.inlineTextAnnotations.includes(doc) || this._props.isContentActive() === false) return 'none'; const isInk = doc.layout_isSvg && !props?.LayoutTemplateString; - return isInk ? 'visiblePainted' : 'all'; + if (isInk) return 'visiblePainted'; } return this._props.styleProvider?.(doc, props, property); }; diff --git a/src/client/views/smartdraw/AnnotationPalette.tsx b/src/client/views/smartdraw/AnnotationPalette.tsx index c296138a8..22b8b7b67 100644 --- a/src/client/views/smartdraw/AnnotationPalette.tsx +++ b/src/client/views/smartdraw/AnnotationPalette.tsx @@ -6,25 +6,25 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { AiOutlineSend } from 'react-icons/ai'; import ReactLoading from 'react-loading'; -import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils'; -import { Doc, DocListCast } from '../../../fields/Doc'; +import { returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils'; +import { emptyFunction } from '../../../Utils'; +import { Doc, DocListCast, returnEmptyDoclist } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; +import { Copy } from '../../../fields/FieldSymbols'; import { ImageCast } from '../../../fields/Types'; -import { emptyFunction } from '../../../Utils'; +import { ImageField } from '../../../fields/URLField'; +import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { makeUserTemplateImage } from '../../util/DropConverter'; import { SettingsManager } from '../../util/SettingsManager'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; +import { ObservableReactComponent } from '../ObservableReactComponent'; +import { DefaultStyleProvider, returnEmptyDocViewList } from '../StyleProvider'; import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView'; import { FieldView } from '../nodes/FieldView'; -import { ObservableReactComponent } from '../ObservableReactComponent'; -import { DefaultStyleProvider } from '../StyleProvider'; import './AnnotationPalette.scss'; import { DrawingOptions, SmartDrawHandler } from './SmartDrawHandler'; -import { DocumentType } from '../../documents/DocumentTypes'; -import { ImageField } from '../../../fields/URLField'; -import { Copy } from '../../../fields/FieldSymbols'; interface AnnotationPaletteProps { Document: Doc; @@ -204,7 +204,7 @@ export class AnnotationPalette extends ObservableReactComponent { const svgStrokes: INode[] = svgObject.children; const strokeData: [InkData, string, string][] = []; // eslint-disable-next-line @typescript-eslint/no-explicit-any - svgStrokes.forEach((child: any) => { + svgStrokes.forEach((child) => { const convertedBezier: InkData = SVGToBezier(child.name, child.attributes); strokeData.push([ convertedBezier.map(point => { -- cgit v1.2.3-70-g09d2 From b1692a7435ae9698eff618bef0e370fe3eb89572 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 29 Aug 2024 13:59:00 -0400 Subject: from last cleanup --- src/client/views/InkStrokeProperties.ts | 140 +++++------ src/client/views/LightboxView.tsx | 3 +- src/client/views/MainView.tsx | 4 +- src/client/views/MarqueeAnnotator.tsx | 9 +- src/client/views/PropertiesView.tsx | 70 ++---- .../collectionFreeForm/CollectionFreeFormView.tsx | 24 +- .../collections/collectionFreeForm/MarqueeView.tsx | 10 +- src/client/views/global/globalScripts.ts | 4 - src/client/views/nodes/LabelBigText.js | 270 --------------------- .../views/nodes/formattedText/FormattedTextBox.tsx | 6 +- src/client/views/pdf/AnchorMenu.tsx | 15 +- src/client/views/pdf/Annotation.tsx | 3 +- src/client/views/smartdraw/SmartDrawHandler.tsx | 10 +- 13 files changed, 127 insertions(+), 441 deletions(-) delete mode 100644 src/client/views/nodes/LabelBigText.js (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index b5cd72fa7..13807c25f 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -1,22 +1,22 @@ import { Bezier } from 'bezier-js'; import * as _ from 'lodash'; import { action, makeObservable, observable, reaction, runInAction } from 'mobx'; +import simplify from 'simplify-js'; import { Doc, NumListCast, Opt } from '../../fields/Doc'; import { InkData, InkField, InkTool } from '../../fields/InkField'; import { List } from '../../fields/List'; import { listSpec } from '../../fields/Schema'; -import { Cast, NumCast, toList } from '../../fields/Types'; +import { Cast, NumCast } from '../../fields/Types'; import { Gestures, PointData } from '../../pen-gestures/GestureTypes'; +import { GestureUtils } from '../../pen-gestures/GestureUtils'; import { Point } from '../../pen-gestures/ndollar'; import { DocumentType } from '../documents/DocumentTypes'; -import { undoBatch, undoable } from '../util/UndoManager'; +import { undoable } from '../util/UndoManager'; import { FitOneCurve } from '../util/bezierFit'; +import { GestureOverlay } from './GestureOverlay'; import { InkingStroke } from './InkingStroke'; import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { DocumentView } from './nodes/DocumentView'; -import simplify from 'simplify-js'; -import { GestureUtils } from '../../pen-gestures/GestureUtils'; -import { GestureOverlay } from './GestureOverlay'; export class InkStrokeProperties { // eslint-disable-next-line no-use-before-define @@ -163,48 +163,46 @@ export class InkStrokeProperties { /** * Deletes the current control point of the selected ink instance. */ - deletePoints = undoable( - (inkView: DocumentView, preserve: boolean) => - this.applyFunction( - inkView, - (view: DocumentView, ink: InkData) => { - const doc = view.Document; - const newPoints = ink.slice(); - const brokenIndices = NumListCast(doc.brokenInkIndices); - if (preserve || this._currentPoint === 0 || this._currentPoint === ink.length - 1 || brokenIndices.includes(this._currentPoint)) { - newPoints.splice(this._currentPoint === 0 ? 0 : this._currentPoint === ink.length - 1 ? this._currentPoint - 3 : this._currentPoint - 2, 4); - } else { - const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4; - const splicedPoints = ink.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8)); - const samples: Point[] = []; - let startDir = { x: 0, y: 0 }; - let endDir = { x: 0, y: 0 }; - for (let i = 0; i < splicedPoints.length / 4; i++) { - const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y }))); - if (i === 0) startDir = bez.derivative(0); - if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1); - for (let t = 0; t < (i === splicedPoints.length / 4 - 1 ? 1 + 1e-7 : 1); t += 0.05) { - const pt = bez.compute(t); - samples.push(new Point(pt.x, pt.y)); - } - } - const { finalCtrls, error } = FitOneCurve(samples, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y }); - if (error < 100) { - newPoints.splice(this._currentPoint - 4, 8, ...finalCtrls); - } else { - newPoints.splice(this._currentPoint - 2, 4); + deletePoints = undoable((inkView: DocumentView, preserve: boolean) => { + this.applyFunction( + inkView, + (view: DocumentView, ink: InkData) => { + const doc = view.Document; + const newPoints = ink.slice(); + const brokenIndices = NumListCast(doc.brokenInkIndices); + if (preserve || this._currentPoint === 0 || this._currentPoint === ink.length - 1 || brokenIndices.includes(this._currentPoint)) { + newPoints.splice(this._currentPoint === 0 ? 0 : this._currentPoint === ink.length - 1 ? this._currentPoint - 3 : this._currentPoint - 2, 4); + } else { + const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4; + const splicedPoints = ink.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8)); + const samples: Point[] = []; + let startDir = { x: 0, y: 0 }; + let endDir = { x: 0, y: 0 }; + for (let i = 0; i < splicedPoints.length / 4; i++) { + const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y }))); + if (i === 0) startDir = bez.derivative(0); + if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1); + for (let t = 0; t < (i === splicedPoints.length / 4 - 1 ? 1 + 1e-7 : 1); t += 0.05) { + const pt = bez.compute(t); + samples.push(new Point(pt.x, pt.y)); } } - doc.brokenInkIndices = new List(brokenIndices.map(control => (control >= this._currentPoint ? control - 4 : control))); - runInAction(() => { - this._currentPoint = -1; - }); - return newPoints.length < 4 ? undefined : newPoints; - }, - true - ), - 'delete ink points' - ); + const { finalCtrls, error } = FitOneCurve(samples, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y }); + if (error < 100) { + newPoints.splice(this._currentPoint - 4, 8, ...finalCtrls); + } else { + newPoints.splice(this._currentPoint - 2, 4); + } + } + doc.brokenInkIndices = new List(brokenIndices.map(control => (control >= this._currentPoint ? control - 4 : control))); + runInAction(() => { + this._currentPoint = -1; + }); + return newPoints.length < 4 ? undefined : newPoints; + }, + true + ); + }, 'delete ink points'); /** * Rotates ink stroke(s) about a point @@ -251,9 +249,8 @@ export class InkStrokeProperties { /** * Handles the movement/scaling of a control point. */ - moveControlPtHandle = undoable( - (inkView: DocumentView, deltaX: number, deltaY: number, controlIndex: number, origInk?: InkData) => - inkView && + moveControlPtHandle = undoable((inkView: DocumentView, deltaX: number, deltaY: number, controlIndex: number, origInk?: InkData) => { + inkView && this.applyFunction(inkView, (view: DocumentView, ink: InkData) => { const order = controlIndex % 4; const closed = InkingStroke.IsClosed(ink); @@ -316,9 +313,8 @@ export class InkStrokeProperties { } return pt; }); - }), - 'move ink ctrl pt' - ); + }); + }, 'move ink ctrl pt'); public static nearestPtToStroke(ctrlPoints: { X: number; Y: number }[], refInkSpacePt: { X: number; Y: number }, excludeSegs?: number[]) { let distance = Number.MAX_SAFE_INTEGER; @@ -471,28 +467,26 @@ export class InkStrokeProperties { /** * Handles the movement/scaling of a handle point. */ - moveTangentHandle = undoable( - (inkView: DocumentView, deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) => - this.applyFunction(inkView, (view: DocumentView, ink: InkData) => { - const doc = view.Document; - const closed = InkingStroke.IsClosed(ink); - const oldHandlePoint = ink[handleIndex]; - const oppositeHandlePoint = ink[oppositeHandleIndex]; - const controlPoint = ink[controlIndex]; - const newHandlePoint = { X: ink[handleIndex].X - deltaX, Y: ink[handleIndex].Y - deltaY }; - const inkCopy = ink.slice(); - inkCopy[handleIndex] = newHandlePoint; - const brokenIndices = Cast(doc.brokenInkIndices, listSpec('number')); - const equivIndex = closed ? (controlIndex === 0 ? ink.length - 1 : controlIndex === ink.length - 1 ? 0 : -1) : -1; - // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle). - if ((!brokenIndices || (!brokenIndices?.includes(controlIndex) && !brokenIndices?.includes(equivIndex))) && (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { - const angle = InkStrokeProperties.angleChange(oldHandlePoint, newHandlePoint, controlPoint); - inkCopy[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); - } - return inkCopy; - }), - 'move ink tangent' - ); + moveTangentHandle = undoable((inkView: DocumentView, deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) => { + this.applyFunction(inkView, (view: DocumentView, ink: InkData) => { + const doc = view.Document; + const closed = InkingStroke.IsClosed(ink); + const oldHandlePoint = ink[handleIndex]; + const oppositeHandlePoint = ink[oppositeHandleIndex]; + const controlPoint = ink[controlIndex]; + const newHandlePoint = { X: ink[handleIndex].X - deltaX, Y: ink[handleIndex].Y - deltaY }; + const inkCopy = ink.slice(); + inkCopy[handleIndex] = newHandlePoint; + const brokenIndices = Cast(doc.brokenInkIndices, listSpec('number')); + const equivIndex = closed ? (controlIndex === 0 ? ink.length - 1 : controlIndex === ink.length - 1 ? 0 : -1) : -1; + // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle). + if ((!brokenIndices || (!brokenIndices?.includes(controlIndex) && !brokenIndices?.includes(equivIndex))) && (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { + const angle = InkStrokeProperties.angleChange(oldHandlePoint, newHandlePoint, controlPoint); + inkCopy[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); + } + return inkCopy; + }); + }, 'move ink tangent'); /** * Function that "smooths" ink strokes by using the gesture recognizer to detect shapes and diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 651fb5888..77a2c1b86 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -22,7 +22,6 @@ import { DefaultStyleProvider, returnEmptyDocViewList, wavyBorderPath } from './ import { DocumentView } from './nodes/DocumentView'; import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere'; import { AnnotationPalette } from './smartdraw/AnnotationPalette'; -import { DocData } from '../../fields/DocSymbols'; interface LightboxViewProps { PanelWidth: number; @@ -58,6 +57,7 @@ export class LightboxView extends ObservableReactComponent { }[] = []; private _savedState: LightboxSavedState = {}; private _history: { doc: Doc; target?: Doc }[] = []; + private _annoPaletteView: AnnotationPalette | null = null; @observable private _future: Doc[] = []; @observable private _layoutTemplate: Opt = undefined; @observable private _layoutTemplateString: Opt = undefined; @@ -65,7 +65,6 @@ export class LightboxView extends ObservableReactComponent { @observable private _docTarget: Opt = undefined; @observable private _docView: Opt = undefined; @observable private _showPalette: boolean = false; - private _annoPaletteView: AnnotationPalette | null = null; @computed get leftBorder() { return Math.min(this._props.PanelWidth / 4, this._props.maxBorder[0]); } // prettier-ignore @computed get topBorder() { return Math.min(this._props.PanelHeight / 4, this._props.maxBorder[1]); } // prettier-ignore diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 2a983a29f..6118cee92 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -580,9 +580,7 @@ export class MainView extends ObservableReactComponent { DocumentManager.removeOverlayViews(); Doc.linkFollowUnhighlight(); const targets = document.elementsFromPoint(e.x, e.y); - const targetClasses: string[] = targets.map(target => { - return target.className.toString(); - }); + const targetClasses = targets.map(target => target.className.toString()); if (targets.length) { let targClass = targets[0].className.toString(); for (let i = 0; i < targets.length - 1; i++) { diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 0d2a11ec4..90323086c 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -27,7 +27,7 @@ export interface MarqueeAnnotatorProps { containerOffset?: () => number[]; marqueeContainer: HTMLDivElement; docView: () => DocumentView; - savedAnnotations: () => ObservableMap; + savedAnnotations: () => ObservableMap; selectionText: () => string; annotationLayer: HTMLDivElement; addDocument: (doc: Doc) => boolean; @@ -60,7 +60,7 @@ export class MarqueeAnnotator extends ObservableReactComponent): Opt => { + makeAnnotationDocument = (color: string, isLinkButton?: boolean, savedAnnotations?: ObservableMap): Opt => { const savedAnnoMap = savedAnnotations?.values() && Array.from(savedAnnotations?.values()).length ? savedAnnotations : this.props.savedAnnotations(); if (savedAnnoMap.size === 0) return undefined; const savedAnnos = Array.from(savedAnnoMap.values())[0]; @@ -128,7 +128,6 @@ export class MarqueeAnnotator extends ObservableReactComponent, addAsAnnotation?: boolean) => { // creates annotation documents for current highlights @@ -138,7 +137,7 @@ export class MarqueeAnnotator extends ObservableReactComponent, annotationLayer: HTMLDivElement & { marqueeing?: boolean}, div: HTMLDivElement, page: number) => { + public static previewNewAnnotation = action((savedAnnotations: ObservableMap, annotationLayer: HTMLDivElement & { marqueeing?: boolean }, div: HTMLDivElement, page: number) => { div.style.backgroundColor = '#ACCEF7'; div.style.opacity = '0.5'; annotationLayer.append(div); @@ -266,7 +265,7 @@ export class MarqueeAnnotator extends ObservableReactComponent { copy.style[prop as unknown as number] = marqueeStyle[prop as unknown as number]; // bcz: hack to get around TS type checking for array index with strings diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index bbe422e9e..6383d4947 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -813,6 +813,9 @@ export class PropertiesView extends ObservableReactComponent { - doc[DocData].stroke_width = Math.round(value * 100) / 100; - }) - } else { - this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Math.round(value * 100) / 100); - } - } // prettier-ignore + set strokeThk(value) { + this.selectedStrokes.forEach(doc => { + doc[DocData].stroke_width = Math.round(value * 100) / 100; + }); + } @computed get hgtInput() { return this.inputBoxDuo( @@ -885,14 +883,9 @@ export class PropertiesView extends ObservableReactComponent { - doc[DocData].color = value || undefined; - }); - } else { - this.selectedDoc && (this.selectedDoc[DocData].color = value || undefined); - } + this.selectedStrokes.forEach(doc => { + doc[DocData].color = value || undefined; + }); } colorButton(value: string, type: string, setter: () => void) { @@ -1019,14 +1012,9 @@ export class PropertiesView extends ObservableReactComponent { - doc[DocData].stroke_dash = value ? this._lastDash : undefined; - }); - } else { - this.selectedDoc && (this.selectedDoc[DocData].stroke_dash = value ? this._lastDash : undefined); - } + this.selectedStrokes.forEach(doc => { + doc[DocData].stroke_dash = value ? this._lastDash : undefined; + }); } @computed get widthStk() { return this.getField('stroke_width') || '1'; } // prettier-ignore set widthStk(value) { @@ -1034,13 +1022,9 @@ export class PropertiesView extends ObservableReactComponent { - doc[DocData].stroke_markerScale = Number(value); - }); - } - this.selectedDoc && (this.selectedDoc[DocData].stroke_markerScale = Number(value)); + this.selectedStrokes.forEach(doc => { + doc[DocData].stroke_markerScale = Number(value); + }); } @computed get smoothAmt() { return Number(this.getField('stroke_smoothAmount') || '10'); } // prettier-ignore set smoothAmt(value) { @@ -1048,23 +1032,15 @@ export class PropertiesView extends ObservableReactComponent { - doc[DocData].stroke_startMarker = value; - }); - } - this.selectedDoc && (this.selectedDoc[DocData].stroke_startMarker = value); + this.selectedStrokes.forEach(doc => { + doc[DocData].stroke_startMarker = value; + }); } @computed get markTail() { return this.getField('stroke_endMarker') || ''; } // prettier-ignore set markTail(value) { - if (this.containsInkDoc) { - const childDocs = DocListCast(this.selectedDoc[DocData].data); - childDocs.forEach(doc => { - doc[DocData].stroke_endMarker = value; - }); - } - this.selectedDoc && (this.selectedDoc[DocData].stroke_endMarker = value); + this.selectedStrokes.forEach(doc => { + doc[DocData].stroke_endMarker = value; + }); } regInput = (key: string, value: string | number | undefined, setter: (val: string) => void) => ( diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2963a8f53..7c7764f1d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -89,8 +89,10 @@ export class CollectionFreeFormView extends CollectionSubView(); private _batch: UndoManager.Batch | undefined = undefined; - private _brushtimer: any; - private _brushtimer1: any; private _keyTimer: NodeJS.Timeout | undefined; // timer for turning off transition flag when key frame change has completed. Need to clear this if you do a second navigation before first finishes, or else first timer can go off during second naviation. private _presEaseFunc: string = 'ease'; @@ -142,12 +142,12 @@ export class CollectionFreeFormView extends CollectionSubView ele.bounds && !ele.bounds.z && ele.inkMask !== -1 && ele.inkMask !== undefined).map(ele => ele.ele); @@ -376,14 +376,14 @@ export class CollectionFreeFormView extends CollectionSubView this._childPointerEvents; - childContentsActive = () => (this._props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)(); + childContentsActive = () => ((this._props.childContentsActive ?? this.isContentActive() === false) ? returnFalse : emptyFunction)(); getChildDocView(entry: PoolData) { const childLayout = entry.pair.layout; const childData = entry.pair.data; @@ -1722,7 +1722,7 @@ export class CollectionFreeFormView extends CollectionSubView (this.Document.isTemplateDoc ? false : !this._renderCutoffData.get(doc[Id] + ''))); + renderCutoffProvider = computedFn((doc: Doc) => (this.Document.isTemplateDoc || this.Document.isTemplateForField ? false : !this._renderCutoffData.get(doc[Id] + ''))); doEngineLayout( poolData: Map, @@ -1982,7 +1982,7 @@ export class CollectionFreeFormView extends CollectionSubView this._props.pinToPres(this.Document, { pinViewport: MarqueeView.CurViewBounds(this.dataDoc, this._props.PanelWidth(), this._props.PanelHeight()) }), icon: 'map-pin' }); - !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' }); + !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: () => this.updateIcon(), icon: 'compress-arrows-alt' }); this._props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' }); this.Document.isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: this.transcribeStrokes, icon: 'font' }); @@ -2080,7 +2080,7 @@ export class CollectionFreeFormView extends CollectionSubView { if (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView?.())) { const layoutUnrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])); - const loadIncrement = this.Document.isTemplateDoc ? Number.MAX_VALUE : 5; + const loadIncrement = this.Document.isTemplateDoc || this.Document.isTemplateForField ? Number.MAX_VALUE : 5; for (let i = 0; i < Math.min(layoutUnrendered.length, loadIncrement); i++) { this._renderCutoffData.set(layoutUnrendered[i][Id] + '', true); } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index f85727661..bfb1d12cb 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -8,7 +8,7 @@ import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSy import { Id } from '../../../../fields/FieldSymbols'; import { InkData, InkTool } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; -import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; +import { Cast, NumCast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; import { GetEffectiveAcl } from '../../../../fields/util'; import { DocUtils } from '../../../documents/DocUtils'; @@ -22,7 +22,7 @@ import { MainView } from '../../MainView'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { MarqueeViewBounds } from '../../PinFuncs'; import { PreviewCursor } from '../../PreviewCursor'; -import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, DocumentView } from '../../nodes/DocumentView'; +import { DocumentView } from '../../nodes/DocumentView'; import { OpenWhere } from '../../nodes/OpenWhere'; import { pasteImageBitmap } from '../../nodes/WebBoxRenderer'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; @@ -30,12 +30,6 @@ import { SubCollectionViewProps } from '../CollectionSubView'; import { ImageLabelBoxData } from './ImageLabelBox'; import { MarqueeOptionsMenu } from './MarqueeOptionsMenu'; import './MarqueeView.scss'; -import { collectionOf, points } from '@turf/turf'; -import { InkingStroke } from '../../InkingStroke'; -import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; -import { Gestures } from '../../../../pen-gestures/GestureTypes'; -import { GestureOverlay } from '../../GestureOverlay'; -import { InkStrokeProperties } from '../../InkStrokeProperties'; interface MarqueeViewProps { getContainerTransform: () => Transform; diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 50319c466..68d287ac8 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -36,9 +36,6 @@ import { ImageBox } from '../nodes/ImageBox'; import { VideoBox } from '../nodes/VideoBox'; import { WebBox } from '../nodes/WebBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; -// import { InkTranscription } from '../InkTranscription'; - -// import { InkTranscription } from '../InkTranscription'; // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function IsNoneSelected() { @@ -367,7 +364,6 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: 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) { - console.log('create inking group '); 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; diff --git a/src/client/views/nodes/LabelBigText.js b/src/client/views/nodes/LabelBigText.js deleted file mode 100644 index 290152cd0..000000000 --- a/src/client/views/nodes/LabelBigText.js +++ /dev/null @@ -1,270 +0,0 @@ -/* -Brorlandi/big-text.js v1.0.0, 2017 -Adapted from DanielHoffmann/jquery-bigtext, v1.3.0, May 2014 -And from Jetroid/bigtext.js v1.0.0, September 2016 - -Usage: -BigText("#myElement",{ - rotateText: {Number}, (null) - fontSizeFactor: {Number}, (0.8) - maximumFontSize: {Number}, (null) - limitingDimension: {String}, ("both") - horizontalAlign: {String}, ("center") - verticalAlign: {String}, ("center") - textAlign: {String}, ("center") - whiteSpace: {String}, ("nowrap") -}); - - -Original Projects: -https://github.com/DanielHoffmann/jquery-bigtext -https://github.com/Jetroid/bigtext.js - -Options: - -rotateText: Rotates the text inside the element by X degrees. - -fontSizeFactor: This option is used to give some vertical spacing for letters that overflow the line-height (like 'g', 'Á' and most other accentuated uppercase letters). This does not affect the font-size if the limiting factor is the width of the parent div. The default is 0.8 - -maximumFontSize: maximum font size to use. - -minimumFontSize: minimum font size to use. if font is calculated smaller than this, text will be rendered at this size and wrapped - -limitingDimension: In which dimension the font size should be limited. Possible values: "width", "height" or "both". Defaults to both. Using this option with values different than "both" overwrites the element parent width or height. - -horizontalAlign: Where to align the text horizontally. Possible values: "left", "center", "right". Defaults to "center". - -verticalAlign: Where to align the text vertically. Possible values: "top", "center", "bottom". Defaults to "center". - -textAlign: Sets the text align of the element. Possible values: "left", "center", "right". Defaults to "center". This option is only useful if there are linebreaks (
tags) inside the text. - -whiteSpace: Sets whitespace handling. Possible values: "nowrap", "pre". Defaults to "nowrap". (Can also be set to enable wrapping but this doesn't work well.) - -Bruno Orlandi - 2017 - -Copyright (C) 2013 Daniel Hoffmann Bernardes, Ícaro Technologies -Copyright (C) 2016 Jet Holt - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -function _calculateInnerDimensions(computedStyle) { - //Calculate the inner width and height - var innerWidth; - var innerHeight; - - var width = parseInt(computedStyle.getPropertyValue("width")); - var height = parseInt(computedStyle.getPropertyValue("height")); - var paddingLeft = parseInt(computedStyle.getPropertyValue("padding-left")); - var paddingRight = parseInt(computedStyle.getPropertyValue("padding-right")); - var paddingTop = parseInt(computedStyle.getPropertyValue("padding-top")); - var paddingBottom = parseInt(computedStyle.getPropertyValue("padding-bottom")); - var borderLeft = parseInt(computedStyle.getPropertyValue("border-left-width")); - var borderRight = parseInt(computedStyle.getPropertyValue("border-right-width")); - var borderTop = parseInt(computedStyle.getPropertyValue("border-top-width")); - var borderBottom = parseInt(computedStyle.getPropertyValue("border-bottom-width")); - - //If box-sizing is border-box, we need to subtract padding and border. - var parentBoxSizing = computedStyle.getPropertyValue("box-sizing"); - if (parentBoxSizing == "border-box") { - innerWidth = width - (paddingLeft + paddingRight + borderLeft + borderRight); - innerHeight = height - (paddingTop + paddingBottom + borderTop + borderBottom); - } else { - innerWidth = width; - innerHeight = height; - } - var obj = {}; - obj["width"] = innerWidth; - obj["height"] = innerHeight; - return obj; -} - -export default function BigText(element, options) { - - if (typeof element === 'string') { - element = document.querySelector(element); - } else if (element.length) { - // Support for array based queries (such as jQuery) - element = element[0]; - } - - var defaultOptions = { - rotateText: null, - fontSizeFactor: 0.8, - maximumFontSize: null, - limitingDimension: "both", - horizontalAlign: "center", - verticalAlign: "center", - textAlign: "center", - whiteSpace: "nowrap", - singleLine: true - }; - - //Merge provided options and default options - options = options || {}; - for (var opt in defaultOptions) - if (defaultOptions.hasOwnProperty(opt) && !options.hasOwnProperty(opt)) - options[opt] = defaultOptions[opt]; - - //Get variables which we will reference frequently - var style = element.style; - var parent = element.parentNode; - var parentStyle = parent.style; - var parentComputedStyle = document.defaultView.getComputedStyle(parent); - - //hides the element to prevent "flashing" - style.visibility = "hidden"; - //Set some properties - style.display = "inline-block"; - style.clear = "both"; - style.float = "left"; - var fontSize = options.maximumFontSize; - if (options.singleLine) { - style.fontSize = (fontSize * options.fontSizeFactor) + "px"; - style.lineHeight = fontSize + "px"; - } else { - for (; fontSize > options.minimumFontSize; fontSize = fontSize - Math.min(fontSize / 2, Math.max(0, fontSize - 48) + 2)) { - style.fontSize = (fontSize * options.fontSizeFactor) + "px"; - style.lineHeight = "1"; - if (element.offsetHeight <= +parentComputedStyle.height.replace("px", "")) { - break; - } - } - } - style.whiteSpace = options.whiteSpace; - style.textAlign = options.textAlign; - style.position = "relative"; - style.padding = 0; - style.margin = 0; - style.left = "50%"; - style.top = "50%"; - var computedStyle = document.defaultView.getComputedStyle(element); - - //Get properties of parent to allow easier referencing later. - var parentPadding = { - top: parseInt(parentComputedStyle.getPropertyValue("padding-top")), - right: parseInt(parentComputedStyle.getPropertyValue("padding-right")), - bottom: parseInt(parentComputedStyle.getPropertyValue("padding-bottom")), - left: parseInt(parentComputedStyle.getPropertyValue("padding-left")), - }; - var parentBorder = { - top: parseInt(parentComputedStyle.getPropertyValue("border-top")), - right: parseInt(parentComputedStyle.getPropertyValue("border-right")), - bottom: parseInt(parentComputedStyle.getPropertyValue("border-bottom")), - left: parseInt(parentComputedStyle.getPropertyValue("border-left")), - }; - - //Calculate the parent inner width and height - var parentInnerDimensions = _calculateInnerDimensions(parentComputedStyle); - var parentInnerWidth = parentInnerDimensions["width"]; - var parentInnerHeight = parentInnerDimensions["height"]; - - var box = { - width: element.offsetWidth, //Note: This is slightly larger than the jQuery version - height: element.offsetHeight, - }; - if (!box.width || !box.height) return element; - - - if (options.rotateText !== null) { - if (typeof options.rotateText !== "number") - throw "bigText error: rotateText value must be a number"; - var rotate = "rotate(" + options.rotateText + "deg)"; - style.webkitTransform = rotate; - style.msTransform = rotate; - style.MozTransform = rotate; - style.OTransform = rotate; - style.transform = rotate; - //calculating bounding box of the rotated element - var sine = Math.abs(Math.sin(options.rotateText * Math.PI / 180)); - var cosine = Math.abs(Math.cos(options.rotateText * Math.PI / 180)); - box.width = element.offsetWidth * cosine + element.offsetHeight * sine; - box.height = element.offsetWidth * sine + element.offsetHeight * cosine; - } - - var parentWidth = (parentInnerWidth - parentPadding.left - parentPadding.right); - var parentHeight = (parentInnerHeight - parentPadding.top - parentPadding.bottom); - var widthFactor = parentWidth / box.width; - var heightFactor = parentHeight / box.height; - var lineHeight; - - if (options.limitingDimension.toLowerCase() === "width") { - lineHeight = Math.floor(widthFactor * fontSize); - } else if (options.limitingDimension.toLowerCase() === "height") { - lineHeight = Math.floor(heightFactor * fontSize); - } else if (widthFactor < heightFactor) - lineHeight = Math.floor(widthFactor * fontSize); - else if (widthFactor >= heightFactor) - lineHeight = Math.floor(heightFactor * fontSize); - - var fontSize = lineHeight * options.fontSizeFactor; - if (fontSize < options.minimumFontSize) { - parentStyle.display = "flex"; - parentStyle.alignItems = "center"; - style.textAlign = "center"; - style.visibility = ""; - style.fontSize = options.minimumFontSize + "px"; - style.lineHeight = ""; - style.overflow = "hidden"; - style.textOverflow = "ellipsis"; - style.top = ""; - style.left = ""; - style.margin = ""; - return element; - } - if (options.maximumFontSize && fontSize > options.maximumFontSize) { - fontSize = options.maximumFontSize; - lineHeight = fontSize / options.fontSizeFactor; - } - - style.fontSize = Math.floor(fontSize) + "px"; - style.lineHeight = Math.ceil(lineHeight) + "px"; - style.marginBottom = "0px"; - style.marginRight = "0px"; - - // if (options.limitingDimension.toLowerCase() === "height") { - // //this option needs the font-size to be set already so computedStyle.getPropertyValue("width") returns the right size - // //this +4 is to compensate the rounding erros that can occur due to the calls to Math.floor in the centering code - // parentStyle.width = (parseInt(computedStyle.getPropertyValue("width")) + 4) + "px"; - // } - - //Calculate the inner width and height - var innerDimensions = _calculateInnerDimensions(computedStyle); - var innerWidth = innerDimensions["width"]; - var innerHeight = innerDimensions["height"]; - - switch (options.verticalAlign.toLowerCase()) { - case "top": - style.top = "0%"; - break; - case "bottom": - style.top = "100%"; - style.marginTop = Math.floor(-innerHeight) + "px"; - break; - default: - style.marginTop = Math.ceil((-innerHeight / 2)) + "px"; - break; - } - - switch (options.horizontalAlign.toLowerCase()) { - case "left": - style.left = "0%"; - break; - case "right": - style.left = "100%"; - style.marginLeft = Math.floor(-innerWidth) + "px"; - break; - default: - style.marginLeft = Math.ceil((-innerWidth / 2)) + "px"; - break; - } - - //shows the element after the work is done - style.visibility = "visible"; - - return element; -} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 38b22fad2..996c6843e 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -265,11 +265,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - const container = DocCast(this._props.Document.embedContainer); + const container = DocCast(this.Document.embedContainer); const docView = DocumentView.getDocumentView?.(container); docView?.ComponentView?._props.addDocument?.(drawing); - drawing.x = NumCast(this._props.Document.x) + (this._props.Document.width as number); - drawing.y = NumCast(this._props.Document.y); + drawing.x = NumCast(this.Document.x) + (this.Document.width as number); + drawing.y = NumCast(this.Document.y); }; AnchorMenu.Instance.setSelectedText(window.getSelection()?.toString() ?? ''); diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 4e0c73b02..1a79bbbfe 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -1,25 +1,24 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components'; -import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction } from 'mobx'; +import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { ColorResult } from 'react-color'; +import ReactLoading from 'react-loading'; import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction, unimplementedFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; +import { DocData } from '../../../fields/DocSymbols'; import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT'; import { Docs } from '../../documents/Documents'; import { SettingsManager } from '../../util/SettingsManager'; +import { undoBatch } from '../../util/UndoManager'; import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu'; import { LinkPopup } from '../linking/LinkPopup'; import { DocumentView } from '../nodes/DocumentView'; +import { DrawingOptions, SmartDrawHandler } from '../smartdraw/SmartDrawHandler'; import './AnchorMenu.scss'; import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup'; -import { DrawingOptions, SmartDrawHandler } from '../smartdraw/SmartDrawHandler'; -import { InkData, InkField } from '../../../fields/InkField'; -import { DocData } from '../../../fields/DocSymbols'; -import { undoBatch } from '../../util/UndoManager'; -import ReactLoading from 'react-loading'; @observer export class AnchorMenu extends AntimodeMenu { @@ -149,9 +148,9 @@ export class AnchorMenu extends AntimodeMenu { gptDraw = async (e: React.PointerEvent) => { try { SmartDrawHandler.Instance.AddDrawing = this.createDrawingAnnotation; - this._isLoading = true; + runInAction(() => (this._isLoading = true)); await SmartDrawHandler.Instance.drawWithGPT({ X: e.clientX, Y: e.clientY }, this._selectedText, 5, 100, true); - this._isLoading = false; + runInAction(() => (this._isLoading = false)); } catch (err) { console.error(err); } diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 3bd42873c..1891cfd4c 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -13,6 +13,7 @@ import { FieldViewProps } from '../nodes/FieldView'; import { OpenWhere } from '../nodes/OpenWhere'; import { AnchorMenu } from './AnchorMenu'; import './Annotation.scss'; +import { Property } from 'csstype'; interface IRegionAnnotationProps { x: number; @@ -45,7 +46,7 @@ interface IAnnotationProps extends FieldViewProps { annoDoc: Doc; containerDataDoc: Doc; fieldKey: string; - pointerEvents?: () => Opt; + pointerEvents?: () => Opt; } @observer export class Annotation extends ObservableReactComponent { diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index 4679941fb..aa10dcead 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -9,7 +9,7 @@ import ReactLoading from 'react-loading'; import { AiOutlineSend } from 'react-icons/ai'; import { gptAPICall, GPTCallType, gptDrawingColor } from '../../apis/gpt/GPT'; import { InkData, InkField, InkTool } from '../../../fields/InkField'; -import { SVGToBezier } from '../../util/bezierFit'; +import { SVGToBezier, SVGType } from '../../util/bezierFit'; import { INode, parse } from 'svgson'; import { Slider, Switch } from '@mui/material'; import { Doc, DocListCast } from '../../../fields/Doc'; @@ -279,14 +279,14 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> { const svgStrokes: INode[] = svgObject.children; const strokeData: [InkData, string, string][] = []; // eslint-disable-next-line @typescript-eslint/no-explicit-any - svgStrokes.forEach((child) => { - const convertedBezier: InkData = SVGToBezier(child.name, child.attributes); + svgStrokes.forEach(child => { + const convertedBezier: InkData = SVGToBezier(child.name as SVGType, child.attributes); strokeData.push([ convertedBezier.map(point => { return { X: point.X + startPoint.X - this._size / 1.5, Y: point.Y + startPoint.Y - this._size / 2 }; }), - (regenerate ? this._lastInput.autoColor : autoColor) ? child.attributes.stroke : undefined, - (regenerate ? this._lastInput.autoColor : autoColor) ? child.attributes.fill : undefined, + (regenerate ? this._lastInput.autoColor : autoColor) ? child.attributes.stroke : '', + (regenerate ? this._lastInput.autoColor : autoColor) ? child.attributes.fill : '', ]); }); return { data: strokeData, lastInput: this._lastInput, lastRes: svg[0] }; -- cgit v1.2.3-70-g09d2 From 692076b1356309111c4f2cb69cbdbf4be1a825bd Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Sun, 22 Sep 2024 15:40:19 -0400 Subject: small bug fixes for smart draw --- src/client/util/Scripting.ts | 4 +- src/client/util/bezierFit.ts | 4 +- src/client/views/DocumentButtonBar.tsx | 15 ---- src/client/views/InkStrokeProperties.ts | 35 ++++------ src/client/views/PropertiesView.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 16 +++-- src/client/views/smartdraw/AnnotationPalette.scss | 46 +++++++++++++ src/client/views/smartdraw/AnnotationPalette.tsx | 56 ++++++++++----- src/client/views/smartdraw/SmartDrawHandler.scss | 41 +++++++++++ src/client/views/smartdraw/SmartDrawHandler.tsx | 80 ++++++++++++++++------ 10 files changed, 213 insertions(+), 90 deletions(-) (limited to 'src/client/views/InkStrokeProperties.ts') diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index c63d3d7cb..cb314e3f1 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -1,7 +1,7 @@ // export const ts = (window as any).ts; // import * as typescriptlib from '!!raw-loader!../../../node_modules/typescript/lib/lib.d.ts' // import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts' -import typescriptlib from 'type_decls.d'; +// import typescriptlib from 'type_decls.d'; import * as ts from 'typescript'; import { Doc, FieldType } from '../../fields/Doc'; import { RefField } from '../../fields/RefField'; @@ -248,7 +248,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp const funcScript = `(function(${paramString})${reqTypes} { ${body} })`; host.writeFile('file.ts', funcScript); - if (typecheck) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib); + // if (typecheck) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib); const program = ts.createProgram(['file.ts'], {}, host); const testResult = program.emit(); const outputText = host.readFile('file.js'); diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts index 4c7f4a0ba..4aef28e6b 100644 --- a/src/client/util/bezierFit.ts +++ b/src/client/util/bezierFit.ts @@ -696,7 +696,7 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] { const matches: RegExpMatchArray[] = Array.from( attributes.d.matchAll(/Q(-?\d+\.?\d*),(-?\d+\.?\d*) (-?\d+\.?\d*),(-?\d+\.?\d*)|C(-?\d+\.?\d*),(-?\d+\.?\d*) (-?\d+\.?\d*),(-?\d+\.?\d*) (-?\d+\.?\d*),(-?\d+\.?\d*)|L(-?\d+\.?\d*),(-?\d+\.?\d*)/g) ); - let lastPt: Point = { X: 0, Y: 0 }; + let lastPt: Point = startPt; matches.forEach(match => { if (match[0].startsWith('Q')) { coordList.push({ X: parseInt(match[1]), Y: parseInt(match[2]) }); @@ -711,7 +711,7 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] { coordList.push({ X: parseInt(match[9]), Y: parseInt(match[10]) }); lastPt = { X: parseInt(match[9]), Y: parseInt(match[10]) }; } else { - coordList.push(lastPt || { X: parseInt(startPt[1]), Y: parseInt(startPt[2]) }); + coordList.push(lastPt); coordList.push({ X: parseInt(match[11]), Y: parseInt(match[12]) }); coordList.push({ X: parseInt(match[11]), Y: parseInt(match[12]) }); coordList.push({ X: parseInt(match[11]), Y: parseInt(match[12]) }); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 096f058ad..04c1d359c 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -239,20 +239,6 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( ); } - saveAnno = undoable(async (targetDoc: Doc) => await AnnotationPalette.addToPalette(targetDoc), 'save to palette'); - - @computed - get saveAnnoButton() { - const targetDoc = this.view0?.Document; - return !targetDoc ? null : ( - {targetDoc.savedAsAnno ? 'Saved as Annotation!' : 'Save to Annotation Palette'}}> -
this.saveAnno(targetDoc)}> - -
-
- ); - } - @computed get shareButton() { const targetDoc = this.view0?.Document; @@ -473,7 +459,6 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
{this.templateButton}
{!DocumentView.Selected().some(v => v.allLinks.length) ? null :
{this.followLinkButton}
}
{this.pinButton}
-
{this.saveAnnoButton}
{this.recordButton}
{this.calendarButton}
{this.keywordButton}
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 13807c25f..5cacde0d4 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -499,31 +499,20 @@ export class InkStrokeProperties { const inkView = DocumentView.getDocumentView(inkDoc); const inkStroke = inkView?.ComponentView as InkingStroke; const { inkData } = inkStroke.inkScaledData(); - - const result = inkData.length > 2 && GestureUtils.GestureRecognizer.Recognize([inkData]); - console.log(result); - if (result && (result.Name === 'line' ? result.Score > 0.92 : result.Score > 0.85)) { - switch (result.Name) { - case Gestures.Line: - case Gestures.Triangle: - case Gestures.Rectangle: - case Gestures.Circle: - GestureOverlay.makeBezierPolygon(inkData, result.Name, true); - break; - default: - } - } else { - const polylinePoints = inkData.filter((pt, index) => { return index % 4 === 0 || pt === inkData.lastElement()}).map(pt => { return { x: pt.X, y: pt.Y }; }); // prettier-ignore - if (polylinePoints.length > 2) { - const toKeep = simplify(polylinePoints, tolerance).map(pt => {return { X: pt.x, Y: pt.y }}); // prettier-ignore - for (var i = 4; i < inkData.length - 3; i += 4) { - const contains = toKeep.find(pt => pt.X === inkData[i].X && pt.Y === inkData[i].Y); - if (!contains) { - this._currentPoint = i; - inkView && this.deletePoints(inkView, false); - } + const polylinePoints = inkData.filter((pt, index) => { return index % 4 === 0 || pt === inkData.lastElement()}).map(pt => { return { x: pt.X, y: pt.Y }; }); // prettier-ignore + if (polylinePoints.length > 2) { + const toKeep = simplify(polylinePoints, tolerance).map(pt => {return { X: pt.x, Y: pt.y }}); // prettier-ignore + for (var i = 4; i < inkData.length - 3; i += 4) { + const contains = toKeep.find(pt => pt.X === inkData[i].X && pt.Y === inkData[i].Y); + if (!contains) { + this._currentPoint = i; + inkView && this.deletePoints(inkView, false); } } + // close the curve if the first and last points are really close (based on tolerance) + if (!InkingStroke.IsClosed(inkData) && Math.sqrt((inkData.lastElement().X - inkData[0].X) ** 2 + (inkData.lastElement().Y - inkData[0].Y) ** 2) <= tolerance * 2) { + inkData[inkData.length - 1] = inkData[0]; + } } }); }, 'smooth ink stroke'); diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 9299705ee..39ed16f5d 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -991,12 +991,12 @@ export class PropertiesView extends ObservableReactComponent { !isNaN(val) && (this.smoothAmt = val); }, - 20, + 10, 1 )} @@ -1021,7 +1021,7 @@ export class PropertiesView extends ObservableReactComponent { + removeDrawing = (useLastContainer: boolean, doc?: Doc) => { this._batch = UndoManager.StartBatch('regenerateDrawing'); - if (doc) { + if (useLastContainer && this._drawingContainer) { + this._props.removeDocument?.(this._drawingContainer); + } else if (doc) { const docData = doc[DocData]; const children = DocListCast(docData.data); this._props.removeDocument?.(doc); this._props.removeDocument?.(children); - } else { - if (this._drawingContainer) this._props.removeDocument?.(this._drawingContainer); } this._drawing = []; }; @@ -1294,6 +1296,7 @@ export class CollectionFreeFormView extends CollectionSubView { const docData = doc[DocData]; docData.title = opts.text.match(/^(.*?)~~~.*$/)?.[1] || opts.text; + docData.width = opts.size; docData.drawingInput = opts.text; docData.drawingComplexity = opts.complexity; docData.drawingColored = opts.autoColor; @@ -2010,6 +2013,11 @@ export class CollectionFreeFormView extends CollectionSubView await AnnotationPalette.addToPalette(this.Document), 'save to palette')), + icon: this.Document.savedAsAnno ? 'clipboard-check' : 'file-arrow-down', + }); this._props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', diff --git a/src/client/views/smartdraw/AnnotationPalette.scss b/src/client/views/smartdraw/AnnotationPalette.scss index 9f875f61a..4f11e8afc 100644 --- a/src/client/views/smartdraw/AnnotationPalette.scss +++ b/src/client/views/smartdraw/AnnotationPalette.scss @@ -8,3 +8,49 @@ border-radius: 5px; margin: auto; } + +.palette-create { + display: flex; + flex-direction: row; + width: 170px; + + .palette-create-input { + color: black; + width: 170px; + } +} + +.palette-create-options { + display: flex; + flex-direction: row; + justify-content: space-between; + width: 170px; + margin-top: 5px; + + .palette-color { + display: flex; + flex-direction: column; + align-items: center; + width: 40px; + } + + .palette-detail, + .palette-size { + display: flex; + flex-direction: column; + align-items: center; + width: 60px; + } +} + +.palette-buttons { + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.palette-save-reset { + display: flex; + flex-direction: row; +} diff --git a/src/client/views/smartdraw/AnnotationPalette.tsx b/src/client/views/smartdraw/AnnotationPalette.tsx index a2d6cc88d..0c8dbf12d 100644 --- a/src/client/views/smartdraw/AnnotationPalette.tsx +++ b/src/client/views/smartdraw/AnnotationPalette.tsx @@ -23,11 +23,22 @@ 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; } +/** + * The AnnotationPalette can be toggled in the lightbox view of a document. The goal of the palette + * is to offer an easy way for users to save then drag and drop repeated annotations onto a document. + * These annotations can be of any annotation type and operate similarly to user templates. + * + * On the "add" side of the palette, there is a way to create a drawing annotation with GPT. Users can + * enter the item to draw, toggle different settings, then GPT will generate three versions of the drawing + * to choose from. These drawings can then be saved to the palette as annotations. + */ @observer export class AnnotationPalette extends ObservableReactComponent { @observable private _paletteMode: 'create' | 'view' = 'view'; @@ -107,20 +118,25 @@ export class AnnotationPalette extends ObservableReactComponent { if (!doc.savedAsAnno) { - Doc.MakeClone(doc).then(cloneMap => - DocumentView.getDocumentView(doc) - ?.ComponentView?.updateIcon?.(true) - .then(() => { - const { clone } = cloneMap; - clone.title = doc.title; - const image = ImageCast(doc.icon, ImageCast(clone[Doc.LayoutFieldKey(clone)]))?.url?.href; - Doc.AddDocToList(Doc.MyAnnos, 'data', makeUserTemplateImage(clone, image)); - doc.savedAsAnno = true; - }) - ); + const docView = DocumentView.getDocumentView(doc); + await docView?.ComponentView?.updateIcon?.(true); + const { clone } = await Doc.MakeClone(doc); + clone.title = doc.title; + const image = ImageCast(doc.icon, ImageCast(clone[Doc.LayoutFieldKey(clone)]))?.url?.href; + Doc.AddDocToList(Doc.MyAnnos, 'data', makeUserTemplateImage(clone, image)); + doc.savedAsAnno = true; } }; + public static getIcon(group: Doc) { + const docView = DocumentView.getDocumentView(group); + if (docView) { + docView.ComponentView?.updateIcon?.(true); + return new Promise(res => setTimeout(() => res(ImageCast(docView.Document.icon)), 1000)); + } + return undefined; + } + /** * Calls the draw with GPT functions in SmartDrawHandler to allow users to generate drawings straight from * the annotation palette. @@ -172,6 +188,8 @@ export class AnnotationPalette extends ObservableReactComponent -
+
{ this.setUserInput(e.target.value); @@ -228,8 +246,8 @@ export class AnnotationPalette extends ObservableReactComponent
-
-
+
+
Color this.setColor(!this._opts.autoColor)} />
-
+
Detail
-
+
Size -
+
diff --git a/src/client/views/smartdraw/SmartDrawHandler.scss b/src/client/views/smartdraw/SmartDrawHandler.scss index 6d402a80f..0e8bd3349 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.scss +++ b/src/client/views/smartdraw/SmartDrawHandler.scss @@ -1,3 +1,44 @@ .smart-draw-handler { position: absolute; } + +.smartdraw-input { + color: black; +} + +.smartdraw-options { + display: flex; + flex-direction: row; + justify-content: space-around; + + .auto-color { + display: flex; + flex-direction: column; + justify-content: center; + width: 30%; + } + + .complexity { + display: flex; + flex-direction: column; + justify-content: center; + width: 31%; + } + + .size { + display: flex; + flex-direction: column; + justify-content: center; + width: 39%; + + .size-slider { + width: 80%; + } + } +} + +.regenerate-box, +.edit-box { + display: flex; + flex-direction: row; +} diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index 8271c0959..e362c0c89 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -12,7 +12,6 @@ import { Doc, DocListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { InkData, InkField, InkTool } from '../../../fields/InkField'; import { BoolCast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; -import { ImageField } from '../../../fields/URLField'; import { GPTCallType, gptAPICall, gptDrawingColor } from '../../apis/gpt/GPT'; import { Docs } from '../../documents/Documents'; import { SettingsManager } from '../../util/SettingsManager'; @@ -34,6 +33,21 @@ export interface DrawingOptions { y: number; } +/** + * The SmartDrawHandler allows users to generate drawings with GPT from text input. Users are able to enter + * the item to draw, how complex they want the drawing to be, how large the drawing should be, and whether + * it will be colored. If the drawing is colored, GPT will automatically define the stroke and fill of each + * stroke. Drawings are retrieved from GPT as SVG code then converted into Dash-supported Beziers. + * + * The handler is selected from the ink tools menu. To generate a drawing, users can click anywhere on the freeform + * canvas and a popup will appear that prompts them to create a drawing. Once the drawing is created, users have + * the option to regenerate or edit the drawing. + * + * When each drawing is created, it is added to Dash as a group of ink strokes. The group is tagged with metadata + * for user input, the drawing's SVG code, and its settings (size, complexity). In the context menu -> 'Options', + * users can then show the drawing editor and regenerate/edit them at any point in the future. + */ + @observer export class SmartDrawHandler extends ObservableReactComponent { static Instance: SmartDrawHandler; @@ -65,8 +79,19 @@ export class SmartDrawHandler extends ObservableReactComponent { SmartDrawHandler.Instance = this; } + /** + * AddDrawing and RemoveDrawing are defined by the other classes that call the smart draw functions (i.e. + CollectionFreeForm, FormattedTextBox, AnnotationPalette) to define how a drawing document should be added + or removed in their respective locations (to the freeform canvs, to the annotation palette's preview, etc.) + */ public AddDrawing: (doc: Doc, opts: DrawingOptions, gptRes: string) => void = unimplementedFunction; - public RemoveDrawing: (doc?: Doc) => void = unimplementedFunction; + public RemoveDrawing: (useLastContainer: boolean, doc?: Doc) => void = unimplementedFunction; + /** + * This creates the ink document that represents a drawing, so it goes through the strokes that make up the drawing, + * creates ink documents for each stroke, then adds the strokes to a collection. This can also be redefined by other + * classes to customize the way the drawing docs get created. For example, the freeform canvas has a different way of + * defining document bounds, so CreateDrawingDoc is redefined when that class calls gpt draw functions. + */ public CreateDrawingDoc: (strokeList: [InkData, string, string][], opts: DrawingOptions, gptRes: string, containerDoc?: Doc) => Doc | undefined = (strokeList: [InkData, string, string][], opts: DrawingOptions) => { const drawing: Doc[] = []; strokeList.forEach((stroke: [InkData, string, string]) => { @@ -101,6 +126,11 @@ export class SmartDrawHandler extends ObservableReactComponent { this._display = true; }; + /** + * This is called in two places: 1. In this class, where the regenerate popup shows as soon as a + * drawing is created to replace the original smart draw popup. 2. From the context menu to make + * the regenerate popup show by user command. + */ @action displayRegenerate = (x: number, y: number) => { this._selectedDoc = DocumentView.SelectedDocs()?.lastElement(); @@ -113,6 +143,9 @@ export class SmartDrawHandler extends ObservableReactComponent { this._lastInput = { text: StrCast(docData.drawingInput), complexity: NumCast(docData.drawingComplexity), size: NumCast(docData.drawingSize), autoColor: BoolCast(docData.drawingColored), x: this._pageX, y: this._pageY }; }; + /** + * Hides the smart draw handler and resets its fields to their default. + */ @action hideSmartDrawHandler = () => { this.ShowRegenerate = false; @@ -126,6 +159,9 @@ export class SmartDrawHandler extends ObservableReactComponent { Doc.ActiveTool = InkTool.None; }; + /** + * Hides the popup that allows users to regenerate a drawing and resets its corresponding fields. + */ @action hideRegenerate = () => { if (!this._isLoading) { @@ -136,12 +172,20 @@ export class SmartDrawHandler extends ObservableReactComponent { } }; + /** + * This allows users to press the return/enter key to send input. + */ handleKeyPress = (event: React.KeyboardEvent) => { if (event.key === 'Enter') { this.handleSendClick(); } }; + /** + * This is called when a user hits "send" on the draw with GPT popup. It calls the drawWithGPT or regenerate + * functions depending on what mode is currently displayed, then sets various observable fields that facilitate + * what the user sees. + */ @action handleSendClick = async () => { this._isLoading = true; @@ -171,7 +215,7 @@ export class SmartDrawHandler extends ObservableReactComponent { }; /** - * Calls GPT API to create a drawing based on user input + * Calls GPT API to create a drawing based on user input. */ @action drawWithGPT = async (startPt: { X: number; Y: number }, input: string, complexity: number, size: number, autoColor: boolean) => { @@ -182,6 +226,7 @@ export class SmartDrawHandler extends ObservableReactComponent { 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); drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); @@ -213,7 +258,7 @@ export class SmartDrawHandler extends ObservableReactComponent { return; } const strokeData = await this.parseSvg(res, { X: this._lastInput.x, Y: this._lastInput.y }, true, lastInput?.autoColor || this._autoColor); - this.RemoveDrawing !== unimplementedFunction && this.RemoveDrawing(this._selectedDoc); + this.RemoveDrawing !== unimplementedFunction && this.RemoveDrawing(true, this._selectedDoc); const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData?.data, strokeData?.lastInput, strokeData?.lastRes); drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); return strokeData; @@ -223,7 +268,7 @@ export class SmartDrawHandler extends ObservableReactComponent { }; /** - * Parses the svg code that GPT returns into Bezier curves. + * Parses the svg code that GPT returns into Bezier curves, with coordinates and colors. */ @action parseSvg = async (res: string, startPoint: { X: number; Y: number }, regenerate: boolean, autoColor: boolean) => { @@ -307,26 +352,18 @@ export class SmartDrawHandler extends ObservableReactComponent { }} icon={} color={SettingsManager.userColor} - style={{ width: '19px' }} /> this._canInteract && (this._userInput = e.target.value))} placeholder="Enter item to draw" onKeyDown={this.handleKeyPress} /> - } - color={SettingsManager.userColor} - style={{ width: '14px' }} - onClick={action(() => (this._showOptions = !this._showOptions))} - /> + } color={SettingsManager.userColor} onClick={action(() => (this._showOptions = !this._showOptions))} />