diff options
author | eleanor-park <eleanor_park@brown.edu> | 2024-08-27 16:22:33 -0400 |
---|---|---|
committer | eleanor-park <eleanor_park@brown.edu> | 2024-08-27 16:22:33 -0400 |
commit | 6f73686ec4dc3e01ae3eacc0150aa59eafea0325 (patch) | |
tree | 392d6ebcb6122326441afbb7dfe69ff4cb583c1d | |
parent | fd5278045e8c2e280d81cb965c0b2cc5afb59be8 (diff) |
pulling from master
-rw-r--r-- | eslint.config.mjs | 17 | ||||
-rw-r--r-- | package-lock.json | 195 | ||||
-rw-r--r-- | package.json | 8 | ||||
-rw-r--r-- | src/client/util/DropConverter.ts | 4 | ||||
-rw-r--r-- | src/client/views/InkStrokeProperties.ts | 7 | ||||
-rw-r--r-- | src/client/views/MarqueeAnnotator.tsx | 145 | ||||
-rw-r--r-- | src/client/views/PropertiesView.scss | 2 | ||||
-rw-r--r-- | src/client/views/PropertiesView.tsx | 52 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 64 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx | 2 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 65 | ||||
-rw-r--r-- | src/client/views/pdf/AnchorMenu.tsx | 20 | ||||
-rw-r--r-- | src/client/views/smartdraw/AnnotationPalette.tsx | 13 | ||||
-rw-r--r-- | src/client/views/smartdraw/SmartDrawHandler.tsx | 22 |
14 files changed, 213 insertions, 403 deletions
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<number, HTMLDivElement[]>; - savedTapes: () => ObservableMap<number, HTMLDivElement[]>; selectionText: () => string; annotationLayer: HTMLDivElement; addDocument: (doc: Doc) => boolean; @@ -75,7 +74,6 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP backgroundColor: color, annotationOn: this.props.Document, title: 'Annotation on ' + this.props.Document.title, - a, }); marqueeAnno.x = NumCast(doc.freeform_panX_min) + (parseInt(anno.style.left || '0') - containerOffset[0]) / scale; marqueeAnno.y = NumCast(doc.freeform_panY_min) + (parseInt(anno.style.top || '0') - containerOffset[1]) / scale; @@ -130,139 +128,6 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP return textRegionAnno; }; - // @undoBatch - // makeTapeDocument = (color: string, isLinkButton?: boolean, savedTapes?: ObservableMap<number, HTMLDivElement[]>): Opt<Doc> => { - // 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<string>(annoRects); - // textRegionAnnoProto.opacity = 0; - // textRegionAnnoProto.layout_unrendered = true; - // savedAnnoMap.clear(); - // return textRegionAnno; - // }; - - @undoBatch - makeTapeDocument = (color: string, isLinkButton?: boolean, savedTapes?: ObservableMap<number, HTMLDivElement[]>): Opt<Doc> => { - // 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<string>(annoRects); - // textRegionAnnoProto.opacity = 0; - // textRegionAnnoProto.layout_unrendered = true; - // savedAnnoMap.clear(); - // return textRegionAnno; - }; - @action highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => { // creates annotation documents for current highlights @@ -272,15 +137,6 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP return annotationDoc as Doc; }; - @action - tape = (color: string, isLinkButton: boolean, savedTapes?: ObservableMap<number, HTMLDivElement[]>, 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<number, HTMLDivElement[]>, annotationLayer: HTMLDivElement, div: HTMLDivElement, page: number) => { div.style.backgroundColor = '#ACCEF7'; div.style.opacity = '0.5'; @@ -327,7 +183,6 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP AnchorMenu.Instance.OnClick = undoable(() => 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<number, HTMLDivElement[]> /* , 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<PropertiesViewProps }; getField(key: string) { - return this.containsInkDoc ? Field.toString(this.inkDoc?.[DocData][key] as FieldType) : Field.toString(this.selectedDoc?.[DocData][key] as FieldType); + return Field.toString(this.selectedDoc?.[DocData][key] as FieldType); } @computed get shapeXps() { return NumCast(this.selectedDoc?.x); } // prettier-ignore @@ -960,6 +960,22 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps const targetDoc = this.selectedLayoutDoc; return ( <div> + {!targetDoc.layout_isSvg && ( + <div className="color"> + <Toggle + text={'Color with GPT'} + color={SettingsManager.userColor} + icon={<FontAwesomeIcon icon="fill-drip" />} + iconPlacement="left" + align="flex-start" + fillWidth + toggleType={ToggleType.BUTTON} + onClick={undoable(() => { + SmartDrawHandler.Instance.colorWithGPT(targetDoc); + }, 'smoothStrokes')} + /> + </div> + )} <div className="smooth"> <Toggle text={'Smooth Ink Strokes'} @@ -988,22 +1004,6 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps 1 )} </div> - {!targetDoc.layout_isSvg && ( - <div className="color"> - <Toggle - text={'Color with GPT'} - color={SettingsManager.userColor} - icon={<FontAwesomeIcon icon="fill-drip" />} - iconPlacement="left" - align="flex-start" - fillWidth - toggleType={ToggleType.BUTTON} - onClick={undoable(() => { - SmartDrawHandler.Instance.colorWithGPT(targetDoc); - }, 'smoothStrokes')} - /> - </div> - )} </div> ); } @@ -1026,9 +1026,15 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps } @computed get markScal() { return Number(this.getField('stroke_markerScale') || '1'); } // prettier-ignore set markScal(value) { + if (this.containsInkDoc) { + const childDocs = DocListCast(this.selectedDoc[DocData].data); + childDocs.forEach(doc => { + 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<PropertiesViewProps childDocs.forEach(doc => { 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<PropertiesViewProps childDocs.forEach(doc => { 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<PropertiesViewProps ); } + /** + * Determines if a selected collection/group document contains any ink strokes to allow users to edit groups + * of ink strokes in the properties menu. + */ containsInk = (selectedDoc: Doc) => { 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<Partial<collection _eraserLock = 0; _eraserPts: number[][] = []; // keep track of the last few eraserPts to make the eraser circle 'stretch' + /** + * Erases strokes by intersecting them with an invisible "eraser stroke". + * By default this iterates through all intersected ink strokes, determines which parts of a stroke need to be erased based on the type + * of eraser, draws back the ink segments to keep, and deletes the original stroke. + * + * Radius eraser: erases strokes by intersecting them with a circle of variable radius. Essentially creates an InkField for the + * eraser circle, then determines its intersections with other ink strokes. Each stroke's DocumentView and its + * intersection t-values are put into a map, which gets looped through to take out the erased parts. + */ erase = (e: PointerEvent, delta: number[]) => { e.stopImmediatePropagation(); const currPoint = { X: e.clientX, Y: e.clientY }; @@ -646,11 +655,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return false; }; - /** - * Erases strokes by intersecting them with an invisible "eraser stroke". - * By default this iterates through all intersected ink strokes, determines their segmentation, draws back the non-intersected segments, - * and deletes the original stroke. - */ @action onEraserMove = (e: PointerEvent, down: number[], delta: number[]) => { this.erase(e, delta); @@ -665,42 +669,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.erase(e, [0, 0]); }; - /** - * Erases strokes by intersecting them with a circle of variable radius. Essentially creates an InkField for the - * eraser circle, then determines its intersections with other ink strokes. Each stroke's DocumentView and its - * intersection t-values are put into a map, which gets looped through to take out the erased parts. - * @param e - * @param down - * @param delta - * @returns - */ - // @action - // onRadiusEraserMove = (e: PointerEvent, down: number[], delta: number[]) => { - // 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<DocumentView, number[]> = 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<Partial<collection return tVals; }; + /** + * Creates an ink document to add to the freeform canvas. + */ createInkDoc = (points: InkData, transformedBounds?: { x: number; y: number; width: number; height: number }) => { 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<Partial<collection _drawing: Doc[] = []; _drawingContainer: Doc | undefined = undefined; + /** + * Function that creates a drawing--a group of ink strokes--to go with the smart draw function. + */ @undoBatch createDrawingDoc = (strokeData: [InkData, string, string][], opts: DrawingOptions, gptRes: string) => { this._drawing = []; @@ -1304,6 +1278,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return collection; }; + /** + * Part of regenerating a drawing--deletes the old drawing. + */ removeDrawing = (doc?: Doc) => { this._batch = UndoManager.StartBatch('regenerateDrawing'); if (doc) { @@ -1317,6 +1294,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this._drawing = []; }; + /** + * Adds the created drawing to the freeform canvas and sets the metadata. + */ addDrawing = (doc: Doc, opts: DrawingOptions, gptRes: string) => { const docData = doc[DocData]; docData.title = opts.text.match(/^(.*?)~~~.*$/)?.[1] || opts.text; @@ -1926,8 +1906,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const locPt = this.ScreenToLocalBoxXf().transformPoint(e.clientX, e.clientY); this._eraserX = locPt[0]; this._eraserY = locPt[1]; - // Doc.ActiveTool === InkTool.RadiusEraser ? this._childPointerEvents = 'none' : this._childPointerEvents = 'all' - // super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); }; @action diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index 76c37dff0..b3fdd9379 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -21,7 +21,6 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { 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<AntimodeMenuProps> { <IconButton tooltip="Delete Documents" onPointerDown={this.delete} icon={<FontAwesomeIcon icon="trash-alt" />} color={this.userColor} /> <IconButton tooltip="Pin selected region" onPointerDown={this.pinWithView} icon={<FontAwesomeIcon icon="map-pin" />} color={this.userColor} /> <IconButton tooltip="Classify Images" onPointerDown={this.classifyImages} icon={<FontAwesomeIcon icon="object-group" />} color={this.userColor} /> - <IconButton tooltip="Smooth Strokes" onPointerDown={() => this.smoothStrokes} icon={<FontAwesomeIcon icon="object-group" />} 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<SubCollectionViewProps MarqueeOptionsMenu.Instance.pinWithView = this.pinWithView; MarqueeOptionsMenu.Instance.classifyImages = this.classifyImages; MarqueeOptionsMenu.Instance.groupImages = this.groupImages; - MarqueeOptionsMenu.Instance.smoothStrokes = this.smoothStrokes; document.addEventListener('pointerdown', hideMarquee, true); document.addEventListener('wheel', hideMarquee, true); } else { @@ -498,70 +497,6 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps }); @undoBatch - smoothStrokes = action((docs?: Doc[]) => { - 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); if (e instanceof KeyboardEvent ? e.key === 'i' : true) { 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<AntimodeMenuProps> { public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public Highlight: (color: string) => Opt<Doc> = (/* color: string */) => undefined; - public Tape: (color: string) => Opt<Doc> = (/* color: string */) => undefined; public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = emptyFunction; public Delete: () => void = unimplementedFunction; public PinToPres: () => void = unimplementedFunction; @@ -144,6 +143,9 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { 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<AntimodeMenuProps> { } }; + /** + * 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<AntimodeMenuProps> { AnchorMenu.Instance.fadeOut(true); }; - @action - tapeClicked = () => { - this.Tape(this.highlightColor); - // AnchorMenu.Instance.fadeOut(true); - }; - @computed get highlighter() { return ( <Group> @@ -219,13 +218,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { colorPicker={this.highlightColor} color={SettingsManager.userColor} /> - <IconButton - tooltip="Click to Add Tape" // - icon={<FontAwesomeIcon icon="tape" />} - onClick={this.tapeClicked} - colorPicker={this.highlightColor} - color={SettingsManager.userColor} - /> <ColorPicker selectedColor={this.highlightColor} setFinalColor={this.changeHighlightColor} setSelectedColor={this.changeHighlightColor} size={Size.XSMALL} /> </Group> ); 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<AnnotationPalett this._props.Document[DocData].data = undefined; }; + /** + * Adds a doc to the annotation palette. Gets a snapshot of the document to use as a preview in the palette. When this + * preview is dragged onto a parent document, a copy of that document is added as an annotation. + */ public static addToPalette = async (doc: Doc) => { if (!doc.savedAsAnno) { const clone = await Doc.MakeClone(doc); @@ -129,6 +133,10 @@ export class AnnotationPalette extends ObservableReactComponent<AnnotationPalett return undefined; } + /** + * Calls the draw with GPT functions in SmartDrawHandler to allow users to generate drawings straight from + * the annotation palette. + */ @undoBatch generateDrawings = action(async () => { this._isLoading = true; @@ -159,6 +167,11 @@ export class AnnotationPalette extends ObservableReactComponent<AnnotationPalett Doc.AddDocToList(this._props.Document, 'data', drawing); }; + /** + * Saves the currently showing, newly generated drawing to the annotation palette and sets the metadata. + * AddToPalette() is generically used to add any document to the palette, while this defines the behavior for when a user + * presses the "save drawing" button. + */ saveDrawing = async () => { 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(/<svg[^>]*>([\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); |