diff --git a/package-lock.json b/package-lock.json index 447b51543a..214172408b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,10 +12,10 @@ "@preact/signals-react": "^2.2.0", "@techstark/opencv-js": "^4.10.0-release.1", "@zip.js/zip.js": "^2.7.52", - "express": "^4.21.0", "konva": "^9.3.14", "onnxruntime-web": "^1.19.2", "react": "^18.3.1", + "react-colorful": "^5.6.1", "react-dom": "^18.3.1", "react-drag-drop-files": "^2.3.10", "react-easy-crop": "^5.0.8", @@ -43,7 +43,6 @@ "eslint-plugin-react-refresh": "^0.4.9", "globals": "^15.9.0", "i": "^0.3.7", - "mrmime": "^2.0.0", "npm": "^10.8.3", "postcss": "^8.4.41", "prettier": "^3.3.3", @@ -1379,18 +1378,6 @@ "node": ">=16.5.0" } }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", @@ -1495,11 +1482,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, "node_modules/array-includes": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", @@ -1699,42 +1681,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1788,18 +1734,11 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -1944,43 +1883,11 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2140,6 +2047,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -2169,23 +2077,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2216,11 +2107,6 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, "node_modules/electron-to-chromium": { "version": "1.5.13", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", @@ -2232,14 +2118,6 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/es-abstract": { "version": "1.23.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", @@ -2304,6 +2182,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, "dependencies": { "get-intrinsic": "^1.2.4" }, @@ -2315,6 +2194,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "engines": { "node": ">= 0.4" } @@ -2462,11 +2342,6 @@ "node": ">=6" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -2756,68 +2631,6 @@ "node": ">=0.10.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2897,36 +2710,6 @@ "node": ">=8" } }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2992,14 +2775,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -3013,14 +2788,6 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/fs-extra": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", @@ -3039,6 +2806,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3082,6 +2850,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", @@ -3207,6 +2976,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -3252,6 +3022,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -3263,6 +3034,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -3274,6 +3046,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -3300,6 +3073,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -3315,21 +3089,6 @@ "react-is": "^16.7.0" } }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/i": { "version": "0.3.7", "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz", @@ -3339,17 +3098,6 @@ "node": ">=0.4" } }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3384,11 +3132,6 @@ "node": ">=0.8.19" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -3403,14 +3146,6 @@ "node": ">= 0.4" } }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -4036,22 +3771,6 @@ "yallist": "^3.0.2" } }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4061,14 +3780,6 @@ "node": ">= 8" } }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -4082,36 +3793,6 @@ "node": ">=8.6" } }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4133,15 +3814,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/mrmime": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", - "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4182,14 +3854,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", @@ -4414,6 +4078,8 @@ }, "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "inBundle": true, "license": "MIT", @@ -5154,6 +4820,8 @@ }, "node_modules/npm/node_modules/cross-spawn/node_modules/which": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "inBundle": true, "license": "ISC", @@ -5202,6 +4870,8 @@ }, "node_modules/npm/node_modules/debug/node_modules/ms": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true, "inBundle": true, "license": "MIT" @@ -5921,6 +5591,8 @@ }, "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "inBundle": true, "license": "ISC", @@ -5947,6 +5619,8 @@ }, "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "inBundle": true, "license": "ISC", @@ -5973,6 +5647,8 @@ }, "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "inBundle": true, "license": "ISC", @@ -6000,6 +5676,8 @@ }, "node_modules/npm/node_modules/minizlib/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "inBundle": true, "license": "ISC", @@ -6657,6 +6335,8 @@ }, "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "inBundle": true, "license": "MIT", @@ -6810,6 +6490,8 @@ }, "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, "inBundle": true, "license": "ISC", @@ -6822,6 +6504,8 @@ }, "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "inBundle": true, "license": "ISC", @@ -6834,6 +6518,8 @@ }, "node_modules/npm/node_modules/tar/node_modules/minipass": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -6934,6 +6620,8 @@ }, "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "inBundle": true, "license": "MIT", @@ -6980,6 +6668,8 @@ }, "node_modules/npm/node_modules/which/node_modules/isexe": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -7028,6 +6718,8 @@ }, "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "inBundle": true, "license": "MIT", @@ -7065,6 +6757,8 @@ }, "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "inBundle": true, "license": "MIT", @@ -7141,6 +6835,7 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -7240,17 +6935,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/onnxruntime-common": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.19.2.tgz", @@ -7334,14 +7018,6 @@ "node": ">=6" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7388,11 +7064,6 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true }, - "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" - }, "node_modules/picocolors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", @@ -7747,18 +7418,6 @@ "node": ">=12.0.0" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -7768,20 +7427,6 @@ "node": ">=6" } }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -7802,28 +7447,6 @@ } ] }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -7835,6 +7458,15 @@ "node": ">=0.10.0" } }, + "node_modules/react-colorful": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", + "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -8089,25 +7721,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/safe-regex-test": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", @@ -8125,11 +7738,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -8146,73 +7754,11 @@ "semver": "bin/semver.js" } }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -8240,11 +7786,6 @@ "node": ">= 0.4" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, "node_modules/shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", @@ -8275,6 +7816,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -8309,14 +7851,6 @@ "node": ">=0.10.0" } }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/stop-iteration-iterator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", @@ -8705,14 +8239,6 @@ "node": ">=8.0" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -8768,18 +8294,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/typed-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", @@ -8918,14 +8432,6 @@ "node": ">= 10.0.0" } }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/update-browserslist-db": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", @@ -8978,14 +8484,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", @@ -8998,14 +8496,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/vite": { "version": "5.4.2", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz", diff --git a/package.json b/package.json index af34014d40..ebf6878726 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "konva": "^9.3.14", "onnxruntime-web": "^1.19.2", "react": "^18.3.1", + "react-colorful": "^5.6.1", "react-dom": "^18.3.1", "react-drag-drop-files": "^2.3.10", "react-easy-crop": "^5.0.8", diff --git a/src/KonvaApp/Engine.ts b/src/KonvaApp/Engine.ts index a7b0aa0b8d..a9bcaf54dc 100644 --- a/src/KonvaApp/Engine.ts +++ b/src/KonvaApp/Engine.ts @@ -3,9 +3,7 @@ import Konva from "konva"; import { RenderEngine } from "./RenderingPrimitives/RenderEngine"; import { ResponseType } from "./WorkerPrimitives/SharedWorkerBase"; -import { uiAccess } from "~/signals"; -import { uiEvents } from "~/signals"; -// import { toolbarNode } from "~/signals/uiAccess/toolbarNode"; +import { uiAccess, uiEvents } from "~/signals"; import { NodesManager, @@ -16,9 +14,8 @@ import { SelectionManagerEvents, SelectorSquare, } from "./NodesManagers"; -import { ImageNode } from "./Nodes/ImageNode"; -import { VideoNode } from "./Nodes/VideoNode"; -import { MediaNode, Position, Transformation } from "./types"; +import { ImageNode, VideoNode, TextNode } from "./Nodes"; +import { MediaNode, Position, TextNodeData, Transformation } from "./types"; import { CreateCommand, @@ -37,11 +34,13 @@ import { DiffusionSharedWorkerProgressData, DiffusionSharedWorkerResponseData, } from "./SharedWorkers/Diffusion/DiffusionSharedWorker"; -import { FileUtilities } from "./FileUtilities/FileUtilities"; import { AppModes, VideoResolutions } from "./constants"; import { ToolbarMainButtonNames } from "~/components/features/ToolbarMain/enum"; +// for testing loading files from system +// import { FileUtilities } from "./FileUtilities/FileUtilities"; + export interface RenderingOptions { artstyle: string; positivePrompt: string; @@ -177,7 +176,7 @@ export class Engine { const videoNode = new VideoNode({ mediaLayerRef: this.mediaLayer, - position: this.renderEngine.captureCanvas.position(), + canvasPosition: this.renderEngine.captureCanvas.position(), canvasSize: this.renderEngine.captureCanvas.size(), videoURL: media_url, selectionManagerRef: this.selectionManager, @@ -208,9 +207,10 @@ export class Engine { if (this.appMode === AppModes.SELECT) { this.selectorSquare.enable({ captureCanvasRef: this.renderEngine.captureCanvas, + mediaLayerRef: this.mediaLayer, nodesManagerRef: this.nodesManager, selectionManagerRef: this.selectionManager, - stage: this.stage, + stageRef: this.stage, }); uiAccess.toolbarMain.changeButtonState(ToolbarMainButtonNames.SELECT, { active: true, @@ -274,6 +274,9 @@ export class Engine { console.log("Engine got video: " + video.url); this.addVideo(video.url); }); + uiEvents.onAddTextToEngine((textdata) => { + this.addText(textdata); + }); uiEvents.onChromakeyRequest((chromakeyProps) => { const node = this.selectionManager @@ -377,10 +380,25 @@ export class Engine { this.addKeyboardShortcuts(); } + public addText(textNodeData: TextNodeData) { + const textNode = new TextNode({ + textNodeData: textNodeData, + mediaLayerRef: this.mediaLayer, + selectionManagerRef: this.selectionManager, + position: { + x: this.renderEngine.captureCanvas.x() + 20, + y: + this.renderEngine.captureCanvas.y() + + this.renderEngine.captureCanvas.height() / 3, + }, + }); + this.createNode(textNode); + } + public addImage(imageFile: File) { const imageNode = new ImageNode({ mediaLayerRef: this.mediaLayer, - position: this.renderEngine.captureCanvas.position(), + canvasPosition: this.renderEngine.captureCanvas.position(), canvasSize: this.renderEngine.captureCanvas.size(), imageFile: imageFile, selectionManagerRef: this.selectionManager, @@ -393,7 +411,7 @@ export class Engine { public addVideo(url: string) { const videoNode = new VideoNode({ mediaLayerRef: this.mediaLayer, - position: this.renderEngine.captureCanvas.position(), + canvasPosition: this.renderEngine.captureCanvas.position(), canvasSize: this.renderEngine.captureCanvas.size(), videoURL: url, selectionManagerRef: this.selectionManager, @@ -417,7 +435,7 @@ export class Engine { }); } - createNode(node: VideoNode | ImageNode) { + createNode(node: VideoNode | ImageNode | TextNode) { const command = new CreateCommand({ nodes: new Set([node]), mediaLayerRef: this.mediaLayer, @@ -488,8 +506,8 @@ export class Engine { } transformNodes(props: { nodes: Set; - initialTransformations: Map; - finalTransformations: Map; + initialTransformations: Map; + finalTransformations: Map; }) { const command = new TransformCommand({ ...props, @@ -503,68 +521,68 @@ export class Engine { * Code for debug and Testing * ********************************/ - public async populateWithDebugItems() { - const imageFile = await FileUtilities.createImageFileFromUrl( - "https://static.miraheze.org/pgrwiki/0/0d/Dialogue-2B-Icon.png", - ); - const imageNode = new ImageNode({ - mediaLayerRef: this.mediaLayer, - position: this.renderEngine.captureCanvas.position(), - canvasSize: this.renderEngine.captureCanvas.size(), - imageFile: imageFile, - selectionManagerRef: this.selectionManager, - }); + // public async populateWithDebugItems() { + // const imageFile = await FileUtilities.createImageFileFromUrl( + // "https://static.miraheze.org/pgrwiki/0/0d/Dialogue-2B-Icon.png", + // ); + // const imageNode = new ImageNode({ + // mediaLayerRef: this.mediaLayer, + // canvasPosition: this.renderEngine.captureCanvas.position(), + // canvasSize: this.renderEngine.captureCanvas.size(), + // imageFile: imageFile, + // selectionManagerRef: this.selectionManager, + // }); - // this.renderEngine.addNodes(videoNode3); + // // this.renderEngine.addNodes(videoNode3); - const videoNode4 = new VideoNode({ - mediaLayerRef: this.mediaLayer, - position: this.renderEngine.captureCanvas.position(), - canvasSize: this.renderEngine.captureCanvas.size(), - videoURL: - "https://storage.googleapis.com/vocodes-public/media/0/2/8/1/n/0281nc0f3kgwvxf8eprywtd01r72rfp6/video_0281nc0f3kgwvxf8eprywtd01r72rfp6.mp4", - selectionManagerRef: this.selectionManager, - }); - const imageNode2 = new ImageNode({ - mediaLayerRef: this.mediaLayer, - position: this.renderEngine.captureCanvas.position(), - canvasSize: this.renderEngine.captureCanvas.size(), - imageFile: imageFile, - selectionManagerRef: this.selectionManager, - }); + // const videoNode4 = new VideoNode({ + // mediaLayerRef: this.mediaLayer, + // canvasPosition: this.renderEngine.captureCanvas.position(), + // canvasSize: this.renderEngine.captureCanvas.size(), + // videoURL: + // "https://storage.googleapis.com/vocodes-public/media/0/2/8/1/n/0281nc0f3kgwvxf8eprywtd01r72rfp6/video_0281nc0f3kgwvxf8eprywtd01r72rfp6.mp4", + // selectionManagerRef: this.selectionManager, + // }); + // const imageNode2 = new ImageNode({ + // mediaLayerRef: this.mediaLayer, + // canvasPosition: this.renderEngine.captureCanvas.position(), + // canvasSize: this.renderEngine.captureCanvas.size(), + // imageFile: imageFile, + // selectionManagerRef: this.selectionManager, + // }); - this.renderEngine.addNodes(videoNode4); + // this.renderEngine.addNodes(videoNode4); - const videoNode5 = new VideoNode({ - mediaLayerRef: this.mediaLayer, - position: this.renderEngine.captureCanvas.position(), - canvasSize: this.renderEngine.captureCanvas.size(), - videoURL: - "https://storage.googleapis.com/vocodes-public/media/0/2/8/1/n/0281nc0f3kgwvxf8eprywtd01r72rfp6/video_0281nc0f3kgwvxf8eprywtd01r72rfp6.mp4", - selectionManagerRef: this.selectionManager, - }); + // const videoNode5 = new VideoNode({ + // mediaLayerRef: this.mediaLayer, + // canvasPosition: this.renderEngine.captureCanvas.position(), + // canvasSize: this.renderEngine.captureCanvas.size(), + // videoURL: + // "https://storage.googleapis.com/vocodes-public/media/0/2/8/1/n/0281nc0f3kgwvxf8eprywtd01r72rfp6/video_0281nc0f3kgwvxf8eprywtd01r72rfp6.mp4", + // selectionManagerRef: this.selectionManager, + // }); - this.renderEngine.addNodes(videoNode5); - // Adding nodes here - const videoNode = new VideoNode({ - mediaLayerRef: this.mediaLayer, - position: this.renderEngine.captureCanvas.position(), - canvasSize: this.renderEngine.captureCanvas.size(), - videoURL: - "https://storage.googleapis.com/vocodes-public/media/r/q/p/r/e/rqpret6mkh18dqwjqwghhdqf15x720s1/storyteller_rqpret6mkh18dqwjqwghhdqf15x720s1.mp4", - selectionManagerRef: this.selectionManager, - }); + // this.renderEngine.addNodes(videoNode5); + // // Adding nodes here + // const videoNode = new VideoNode({ + // mediaLayerRef: this.mediaLayer, + // canvasPosition: this.renderEngine.captureCanvas.position(), + // canvasSize: this.renderEngine.captureCanvas.size(), + // videoURL: + // "https://storage.googleapis.com/vocodes-public/media/r/q/p/r/e/rqpret6mkh18dqwjqwghhdqf15x720s1/storyteller_rqpret6mkh18dqwjqwghhdqf15x720s1.mp4", + // selectionManagerRef: this.selectionManager, + // }); - // CODE TO TEST RENDER ENGINE - // Testing render engine - // this.renderEngine.addNodes(videoNode); - // await this.renderEngine.startProcessing(); - //videoNode.simulatedLoading(); - // TODO support Text nodes + // // CODE TO TEST RENDER ENGINE + // // Testing render engine + // // this.renderEngine.addNodes(videoNode); + // // await this.renderEngine.startProcessing(); + // //videoNode.simulatedLoading(); + // // TODO support Text nodes - // this.renderEngine.addNodes(imageNode2); + // // this.renderEngine.addNodes(imageNode2); - // // TODO if only image take image and just takes snapshots Edge case - // this.renderEngine.addNodes(imageNode); - } + // // // TODO if only image take image and just takes snapshots Edge case + // // this.renderEngine.addNodes(imageNode); + // } } diff --git a/src/KonvaApp/Nodes/BaseNode.ts b/src/KonvaApp/Nodes/BaseNode.ts new file mode 100644 index 0000000000..0a91394681 --- /dev/null +++ b/src/KonvaApp/Nodes/BaseNode.ts @@ -0,0 +1,195 @@ +import Konva from "konva"; +import { v4 as uuidv4 } from "uuid"; + +import { SelectionManager } from "../NodesManagers"; +import { highlightStrokeWidth, primaryOrange } from "./constants"; + +export class BaseNode { + protected selectionManagerRef: SelectionManager; + public kNode: Konva.Node; + protected mediaLayerRef: Konva.Layer; + protected uuid: string; + + // Internal State members + // do not modify internal + protected isSelecting: boolean = false; + // This locks interaction when the render engine is rendering + protected isProcessing: boolean = false; + protected _isLocked: boolean = false; + protected _isKEventRef: boolean = false; + + constructor({ + selectionManagerRef, + mediaLayerRef, + kNode, + }: { + selectionManagerRef: SelectionManager; + mediaLayerRef: Konva.Layer; + kNode: Konva.Image | Konva.Group; + }) { + console.log("Node constructed"); + this.uuid = uuidv4(); + this.mediaLayerRef = mediaLayerRef; + this.selectionManagerRef = selectionManagerRef; + this.kNode = kNode; + } + + protected delete() { + // Do any other clean up and delete the konva node. + this.kNode.destroy(); + } + protected async setProcessing() { + this.isProcessing = true; + } + public isKEventRef() { + return this._isKEventRef; + } + public setIsKEventRef(flag: boolean) { + this._isKEventRef = flag; + this.removeListenToBaseKNodeTransformations(); + this.removeListenToBaseKNodeDrags(); + if (flag) { + this.listenToBaseKNodeTransformations(); + this.listenToBaseKNodeDrags(); + } + } + public highlight() { + // console.log("Highlight", this.kNode._id); + if (this.kNode instanceof Konva.Image) { + this.kNode.stroke(primaryOrange); + this.kNode.strokeWidth(highlightStrokeWidth); + return; + } + if (this.kNode instanceof Konva.Group) { + const wrapperRect = this.kNode.findOne(".wrapper") as Konva.Rect; + wrapperRect.stroke(primaryOrange); + wrapperRect.strokeWidth(highlightStrokeWidth); + return; + } + if (import.meta.env.DEV) { + console.warn( + "DEV: BaseNode Highlight Error, kNode of BaseNode should be override by MediaNodes that extends kNode", + ); + } + } + + public unhighlight() { + // console.log("unHighlight", this.kNode._id); + if (this.kNode instanceof Konva.Image) { + this.kNode.strokeWidth(0); + return; + } + if (this.kNode instanceof Konva.Group) { + const wrapperRect = this.kNode.findOne(".wrapper") as Konva.Rect; + wrapperRect.strokeWidth(0); + return; + } + if (import.meta.env.DEV) { + console.warn( + "DEV: BaseNode UnHighlight Error, kNode of BaseNode should be override by MediaNodes that extends kNode", + ); + } + } + + public moveLayerUp() { + this.kNode.moveUp(); + } + public moveLayerDown() { + this.kNode.moveDown(); + } + public isLocked() { + return this._isLocked; + } + public lock() { + this._isLocked = true; + this.kNode.setDraggable(false); + if (this._isKEventRef) { + this.selectionManagerRef.updateContextComponents(this); + } + } + public unlock() { + this._isLocked = false; + this.kNode.setDraggable(true); + if (this._isKEventRef) { + this.selectionManagerRef.updateContextComponents(this); + } + } + + public listenToBaseKNode() { + const handleSelect = (isMultiSelect: boolean) => { + if (!isMultiSelect) { + // clear selection if not multislect + //console.log("No Shift >> no multiselect"); + this.selectionManagerRef.clearSelection(); + } + if (this.selectionManagerRef.isNodeSelected(this)) { + this.selectionManagerRef.deselectNode(this); + return; + } + this.selectionManagerRef.selectNode(this); + }; + + this.kNode.on("mousedown", (e) => { + // console.log("Mouse down"); + // Selection of Node + if (!this.selectionManagerRef.isNodeSelected(this)) { + this.isSelecting = true; + //checking for multiselect + handleSelect(e.evt.shiftKey); + } + }); + + this.kNode.on("mouseup", (e) => { + // console.log("MOUSE UP"); + + // just coming out of dragging or selecting mode + // no need to handle select + if (this.selectionManagerRef.isDragging() || this.isSelecting) { + this.isSelecting = false; + return; + } + + // checking for multiselect, in multiselect deselection is possible + handleSelect(e.evt.shiftKey); + }); + } + public listenToBaseKNodeDrags() { + this.kNode.on("dragstart", () => { + // console.log("Drag start", this.kNode._id); + if (this.isProcessing) { + return; + } + this.selectionManagerRef.dragStart(); + }); + + this.kNode.on("dragend", () => { + // console.log("Drag End", this.kNode._id); + this.selectionManagerRef.dragEnd(this); + }); + } + public removeListenToBaseKNodeDrags() { + this.kNode.removeEventListener("dragstart"); + this.kNode.removeEventListener("dragend"); + } + public listenToBaseKNodeTransformations() { + this.kNode.on("transformstart", (event) => { + console.log("transformstart", event.target._id); + this.selectionManagerRef.transformStart(); + }); + this.kNode.on("transform", () => { + this.kNode.setAttrs({ + width: this.kNode.width() * this.kNode.scaleX(), + height: this.kNode.height() * this.kNode.scaleY(), + scaleX: 1, + scaleY: 1, + }); + }); + this.kNode.on("transformend", (event) => { + console.log("transformend", event.target._id); + this.selectionManagerRef.transformEnd(this); + }); + } + public removeListenToBaseKNodeTransformations() { + this.kNode.removeEventListener("transformend"); + } +} diff --git a/src/KonvaApp/Nodes/ImageNode.ts b/src/KonvaApp/Nodes/ImageNode.ts index d7787f2122..3b83f66a87 100644 --- a/src/KonvaApp/Nodes/ImageNode.ts +++ b/src/KonvaApp/Nodes/ImageNode.ts @@ -1,23 +1,26 @@ import Konva from "konva"; -import { NetworkedNodeContext } from "./NetworkedNodeContext"; +import { NetworkedNode } from "./NetworkedNode"; import { SelectionManager } from "../NodesManagers"; import { Position, Size } from "../types"; +import { minNodeSize, transparent } from "./constants"; +import { NodeUtilities } from "./NodeUtilities"; interface ImageNodeContructor { - position: Position; + canvasPosition: Position; canvasSize: Size; imageFile: File; mediaLayerRef: Konva.Layer; selectionManagerRef: SelectionManager; } -export class ImageNode extends NetworkedNodeContext { - public imageURL: string; +export class ImageNode extends NetworkedNode { + // public imageURL: string; + public kNode: Konva.Image; private imageObject: HTMLImageElement; constructor({ - position, + canvasPosition, canvasSize, imageFile, mediaLayerRef, @@ -25,13 +28,16 @@ export class ImageNode extends NetworkedNodeContext { }: ImageNodeContructor) { // kNodes need to be created first to guaruntee it is not undefined in parent's context const kNode = new Konva.Image({ - x: position.x, - y: position.y, image: undefined, // to do replace with placeholder - width: 100, - height: 100, + size: minNodeSize, + position: NodeUtilities.positionNodeOnCanvasCenter({ + canvasOffset: canvasPosition, + componentSize: minNodeSize, + maxSize: canvasSize, + }), fill: "gray", draggable: true, + strokeScaleEnabled: false, }); super({ @@ -39,6 +45,7 @@ export class ImageNode extends NetworkedNodeContext { mediaLayerRef: mediaLayerRef, kNode: kNode, }); + this.kNode = kNode; this.mediaLayerRef.add(this.kNode); const imageComponent = new Image(); @@ -51,19 +58,24 @@ export class ImageNode extends NetworkedNodeContext { return; } - const renderSize = this.calculateRenderSizeOnLoad({ + const adjustedSize = NodeUtilities.adjustNodeSizeToCanvas({ componentSize: { width: imageComponent.width, height: imageComponent.height, }, maxSize: canvasSize, }); - + const centerPosition = NodeUtilities.positionNodeOnCanvasCenter({ + canvasOffset: canvasPosition, + componentSize: adjustedSize, + maxSize: canvasSize, + }); this.kNode.image(imageComponent); - this.kNode.setSize(renderSize); + this.kNode.setSize(adjustedSize); + this.kNode.setPosition(centerPosition); this.listenToBaseKNode(); - this.kNode.fill(null); + this.kNode.fill(transparent); this.mediaLayerRef.draw(); this.didFinishLoading = true; @@ -82,7 +94,7 @@ export class ImageNode extends NetworkedNodeContext { this.kNode.image(newImage); this.kNode.width(newImage.width); this.kNode.height(newImage.height); - this.kNode.fill(null); + this.kNode.fill(transparent); this.kNode.draw(); }; } diff --git a/src/KonvaApp/Nodes/NetworkedNode.ts b/src/KonvaApp/Nodes/NetworkedNode.ts new file mode 100644 index 0000000000..2650fe5f4c --- /dev/null +++ b/src/KonvaApp/Nodes/NetworkedNode.ts @@ -0,0 +1,26 @@ +import Konva from "konva"; +import { SelectionManager } from "../NodesManagers"; +import { BaseNode } from "./BaseNode"; + +export class NetworkedNode extends BaseNode { + public kNode: Konva.Image; + public didFinishLoading: boolean = false; + + constructor({ + kNode, + selectionManagerRef, + mediaLayerRef, + }: { + kNode: Konva.Image; + selectionManagerRef: SelectionManager; + mediaLayerRef: Konva.Layer; + }) { + super({ + kNode, + selectionManagerRef, + mediaLayerRef, + }); + this.kNode = kNode; + this.didFinishLoading = false; + } +} diff --git a/src/KonvaApp/Nodes/NetworkedNodeContext.ts b/src/KonvaApp/Nodes/NetworkedNodeContext.ts deleted file mode 100644 index ae3f984c9b..0000000000 --- a/src/KonvaApp/Nodes/NetworkedNodeContext.ts +++ /dev/null @@ -1,252 +0,0 @@ -import Konva from "konva"; -import { v4 as uuidv4 } from "uuid"; - -import { Layer } from "konva/lib/Layer"; - -import { uiAccess } from "~/signals"; -import { SelectionManager } from "../NodesManagers"; -import { Size } from "../types"; - -const toolbarNode = uiAccess.toolbarNode; -const loadingBar = uiAccess.loadingBar; - -export class NetworkedNodeContext { - public selectionManagerRef: SelectionManager; - public kNode: Konva.Image; - protected mediaLayerRef: Layer; - public uuid: string; - - // Internal State members - // do not modify internal - public didFinishLoading: boolean = false; - // This locks interaction when the render engine is rendering - protected isProcessing: boolean = false; - protected isSelecting: boolean = false; - protected _isLocked: boolean = false; - - async setProcessing() { - this.isProcessing = true; - } - - constructor({ - selectionManagerRef, - mediaLayerRef, - kNode, - }: { - selectionManagerRef: SelectionManager; - mediaLayerRef: Layer; - kNode: Konva.Image; - }) { - console.log("reconstructed"); - this.uuid = uuidv4(); - this.mediaLayerRef = mediaLayerRef; - this.selectionManagerRef = selectionManagerRef; - this.kNode = kNode; - this.didFinishLoading = false; - } - protected calculateRenderSizeOnLoad({ - componentSize, - maxSize, - }: { - componentSize: Size; - maxSize: Size; - }) { - const renderSize = { - width: componentSize.width, - height: componentSize.height, - }; - if (renderSize.width > maxSize.width) { - const scaleDown = maxSize.width / renderSize.width; - renderSize.width = renderSize.width * scaleDown; - renderSize.height = renderSize.height * scaleDown; - } - if (renderSize.height > maxSize.height) { - const scaleDownAgain = maxSize.height / renderSize.height; - renderSize.width = renderSize.width * scaleDownAgain; - renderSize.height = renderSize.height * scaleDownAgain; - } - return renderSize; - } - public delete() { - // Do any other clean up and delete the konva node. - this.kNode.destroy(); - } - - public highlight() { - this.kNode.stroke("salmon"); - this.kNode.strokeWidth(10); - } - - public unHighLight() { - console.log("unHighlight", this.kNode._id); - this.kNode.strokeWidth(0); - } - public moveLayerUp() { - this.kNode.moveUp(); - } - public moveLayerDown() { - this.kNode.moveDown(); - } - public isLocked() { - return this._isLocked; - } - public lock() { - this._isLocked = true; - toolbarNode.lock(); - this.kNode.setDraggable(false); - } - public unlock() { - this._isLocked = false; - toolbarNode.unlock(); - this.kNode.setDraggable(true); - } - - calculateContextualsPosition(kNode: Konva.Node) { - const w = kNode.getSize().width * kNode.scaleX(); - const h = kNode.getSize().height * kNode.scaleY(); - const x0 = kNode.getPosition().x; - const y0 = kNode.getPosition().y; - - const d = kNode.getAbsoluteRotation(); - const r = d >= 0 ? (d * Math.PI) / 180 : ((360 + d) * Math.PI) / 180; - - let px: number, py: number; - if (r < Math.PI / 2) { - px = x0 + (h * Math.sin(r) + w * Math.cos(r)) / 2 - h * Math.sin(r); - py = y0 + h * Math.cos(r) + w * Math.cos(Math.PI / 2 - r); - } else if (r < Math.PI) { - px = x0 - (h * Math.sin(r) - w * Math.cos(r)) / 2; - py = y0 + w * Math.cos(Math.PI / 2 - r); - } else if (r < (Math.PI * 3) / 2) { - px = x0 + h * Math.cos(r) - (h * Math.cos(r) + w * Math.sin(r)) / 2; - py = y0; - } else { - px = x0 + (-h * Math.sin(r) + w * Math.cos(r)) / 2; - py = y0 + h * Math.cos(r); - } - return { x: px, y: py }; - } - updateContextMenuPosition() { - const coord = this.calculateContextualsPosition(this.kNode); - toolbarNode.setPosition({ - x: coord.x, - y: coord.y, - }); - } - - updateLoadingBarPosition() { - const coord = this.calculateContextualsPosition(this.kNode); - - loadingBar.updatePosition({ - x: coord.x, - y: coord.y, - }); - } - - public updateContextComponents() { - this.updateContextMenuPosition(); - this.updateLoadingBarPosition(); - if (this.didFinishLoading == false && !loadingBar.isShowing()) { - loadingBar.show(); - } else { - loadingBar.hide(); - } - if (!toolbarNode.isShowing()) { - toolbarNode.show({ - locked: this._isLocked, - }); - } else { - toolbarNode.update({ - locked: this._isLocked, - }); - } - } - - public listenToBaseKNode() { - const handleSelect = (isMultiSelect: boolean) => { - if (!isMultiSelect) { - // clear selection if not multislect - //console.log("No Shift >> no multiselect"); - this.selectionManagerRef.clearSelection(); - } - if (this.selectionManagerRef.isNodeSelected(this)) { - this.selectionManagerRef.deselectNode(this); - return; - } - this.selectionManagerRef.selectNode(this); - }; - - this.kNode.on("mousedown", (e) => { - // console.log("Mouse down"); - // Selection of Node - if (!this.selectionManagerRef.isNodeSelected(this)) { - this.isSelecting = true; - //checking for multiselect - handleSelect(e.evt.shiftKey); - } - - if (!toolbarNode.isShowing()) { - toolbarNode.show({ - locked: this._isLocked, - }); - } - }); - - this.kNode.on("mouseup", (e) => { - // console.log("MOUSE UP"); - - // just coming out of dragging or selecting mode - // no need to handle select - if (this.selectionManagerRef.isDragging() || this.isSelecting) { - this.isSelecting = false; - return; - } - - // checking for multiselect, in multiselect deselection is possible - handleSelect(e.evt.shiftKey); - }); - } - public listenToBaseKNodeDrags() { - this.kNode.on("dragstart", () => { - // console.log("Drag start", this.kNode._id); - if (this.isProcessing) { - return; - } - this.selectionManagerRef.dragStart(); - this.updateContextComponents(); - }); - - this.kNode.on("dragmove", () => { - // console.log("Drag Move"); - this.updateContextComponents(); - }); - - this.kNode.on("dragend", () => { - // console.log("Drag End", this.kNode._id); - this.selectionManagerRef.dragEnd(); - this.updateContextComponents(); - }); - } - public removeListenToBaseKNodeDrags() { - this.kNode.removeEventListener("dragstart"); - this.kNode.removeEventListener("dragmove"); - this.kNode.removeEventListener("dragend"); - } - public listenToBaseKNodeTransformations() { - this.kNode.on("transformstart", (event) => { - console.log("transformstart", event.target._id); - this.selectionManagerRef.transformStart(); - toolbarNode.hide(); - loadingBar.hide(); - }); - - this.kNode.on("transformend", (event) => { - console.log("transformend", event.target._id); - this.selectionManagerRef.transformEnd(); - this.updateContextComponents(); - }); - } - public removeListenToBaseKNodeTransformations() { - this.kNode.removeEventListener("transformend"); - } -} diff --git a/src/KonvaApp/Nodes/NodeUtilities.ts b/src/KonvaApp/Nodes/NodeUtilities.ts new file mode 100644 index 0000000000..97a59df050 --- /dev/null +++ b/src/KonvaApp/Nodes/NodeUtilities.ts @@ -0,0 +1,55 @@ +import Konva from "konva"; +import { Position, Size } from "../types"; + +export const NodeUtilities = { + adjustNodeSizeToCanvas, + positionNodeOnCanvasCenter, + printKNodeAttrs, +}; +function adjustNodeSizeToCanvas({ + componentSize, + maxSize, +}: { + componentSize: Size; + maxSize: Size; +}) { + const adjustedSize = { + width: componentSize.width, + height: componentSize.height, + }; + if (adjustedSize.width > maxSize.width) { + const scaleDown = maxSize.width / adjustedSize.width; + adjustedSize.width = adjustedSize.width * scaleDown; + adjustedSize.height = adjustedSize.height * scaleDown; + } + if (adjustedSize.height > maxSize.height) { + const scaleDownAgain = maxSize.height / adjustedSize.height; + adjustedSize.width = adjustedSize.width * scaleDownAgain; + adjustedSize.height = adjustedSize.height * scaleDownAgain; + } + return adjustedSize; +} + +function positionNodeOnCanvasCenter({ + canvasOffset, + componentSize, + maxSize, +}: { + canvasOffset: Position; + componentSize: Size; + maxSize: Size; +}) { + return { + x: canvasOffset.x + maxSize.width / 2 - componentSize.width / 2, + y: canvasOffset.y + maxSize.height / 2 - componentSize.height / 2, + }; +} + +function printKNodeAttrs(kNode: Konva.Node) { + console.log({ + position: kNode.position(), + size: kNode.size(), + scale: kNode.scale(), + rotation: kNode.rotation(), + }); +} diff --git a/src/KonvaApp/Nodes/TextNodes.ts b/src/KonvaApp/Nodes/TextNodes.ts new file mode 100644 index 0000000000..afb31d4287 --- /dev/null +++ b/src/KonvaApp/Nodes/TextNodes.ts @@ -0,0 +1,98 @@ +import Konva from "konva"; +import { SelectionManager } from "../NodesManagers"; +import { BaseNode } from "./BaseNode"; +import { Position, Size, TextNodeData } from "../types"; +import { NodeUtilities } from "./NodeUtilities"; + +export class TextNode extends BaseNode { + public kNode: Konva.Group; + public rectNode: Konva.Rect; + public textNode: Konva.Text; + private originalTextSize: Size; + private textNodeData: TextNodeData; + + public didFinishLoading: boolean = true; + + constructor({ + textNodeData, + selectionManagerRef, + mediaLayerRef, + position, + }: { + textNodeData: TextNodeData; + selectionManagerRef: SelectionManager; + mediaLayerRef: Konva.Layer; + position: Position; + }) { + console.log(textNodeData); + const textNode = new Konva.Text({ + ...textNodeData, + x: 10, + y: 10, + width: 500, + }); + const kNode = new Konva.Group({ + position: position, + width: textNode.width() + 20, + height: textNode.height() + 20, + draggable: true, + }); + const rectNode = new Konva.Rect({ + x: 0, + y: 0, + width: textNode.width() + 20, + height: textNode.height() + 20, + strokeScaleEnabled: false, + name: "wrapper", + }); + + super({ + selectionManagerRef: selectionManagerRef, + mediaLayerRef: mediaLayerRef, + kNode: kNode, + }); + this.textNodeData = textNodeData; + this.kNode = kNode; + this.textNode = textNode; + this.rectNode = rectNode; + this.kNode.add(this.rectNode); + this.kNode.add(this.textNode); + this.mediaLayerRef.add(this.kNode); + this.originalTextSize = this.textNode.size(); + this.listenToBaseKNode(); + } + public listenToBaseKNodeTransformations() { + this.kNode.on("transformstart", () => { + // console.log("transformstart", event.target._id); + this.selectionManagerRef.transformStart(); + }); + + this.kNode.on("transform", () => { + const newBoxSize = { + width: this.kNode.width() * this.kNode.scaleX(), + height: this.kNode.height() * this.kNode.scaleY(), + scaleX: 1, + scaleY: 1, + }; + this.rectNode.setAttrs(newBoxSize); + this.kNode.setAttrs(newBoxSize); + const scaleX = (newBoxSize.width - 20) / this.originalTextSize.width; + const scaleY = (newBoxSize.height - 20) / this.originalTextSize.height; + this.textNode.setAttrs({ + // make sure these stay the same + x: 10, + y: 10, + size: this.originalTextSize, + //while these changes in size + scaleX: scaleX, + scaleY: scaleY, + }); + }); + this.kNode.on("transformend", () => { + // console.log("transformend", event.target._id); + NodeUtilities.printKNodeAttrs(this.kNode); + NodeUtilities.printKNodeAttrs(this.textNode); + this.selectionManagerRef.transformEnd(this); + }); + } +} diff --git a/src/KonvaApp/Nodes/VideoNode.ts b/src/KonvaApp/Nodes/VideoNode.ts index 1389c26bd4..09051849b2 100644 --- a/src/KonvaApp/Nodes/VideoNode.ts +++ b/src/KonvaApp/Nodes/VideoNode.ts @@ -1,22 +1,24 @@ import Konva from "konva"; -import { NetworkedNodeContext } from "./NetworkedNodeContext"; +import { NetworkedNode } from "./NetworkedNode"; import { uiAccess } from "~/signals"; import { SelectionManager } from "../NodesManagers"; import { Position, Size } from "../types"; import ChromaWorker from "./ChromaWorker?sharedworker"; +import { minNodeSize, transparent } from "./constants"; +import { NodeUtilities } from "./NodeUtilities"; -// const toolbarNode = uiAccess.toolbarNode; const loadingBar = uiAccess.loadingBar; interface VideoNodeContructor { mediaLayerRef: Konva.Layer; - position: Position; + canvasPosition: Position; canvasSize: Size; videoURL: string; selectionManagerRef: SelectionManager; } -export class VideoNode extends NetworkedNodeContext { +export class VideoNode extends NetworkedNode { + public kNode: Konva.Image; public videoURL: string; public videoComponent: HTMLVideoElement; @@ -91,7 +93,7 @@ export class VideoNode extends NetworkedNodeContext { constructor({ mediaLayerRef, - position, + canvasPosition, canvasSize, videoURL, selectionManagerRef, @@ -99,12 +101,16 @@ export class VideoNode extends NetworkedNodeContext { // kNodes need to be created first to guaruntee it is not undefined in parent's context const kNode = new Konva.Image({ image: undefined, - x: position.x, - y: position.y, - width: 200, // to do fix this with placeholder - height: 200, - draggable: true, + // to do fix this with placeholder + size: minNodeSize, + position: NodeUtilities.positionNodeOnCanvasCenter({ + canvasOffset: canvasPosition, + componentSize: minNodeSize, + maxSize: canvasSize, + }), fill: "grey", + draggable: true, + strokeScaleEnabled: false, }); super({ @@ -112,6 +118,7 @@ export class VideoNode extends NetworkedNodeContext { mediaLayerRef: mediaLayerRef, kNode: kNode, }); + this.kNode = kNode; this.mediaLayerRef.add(this.kNode); // state manage the node @@ -150,22 +157,28 @@ export class VideoNode extends NetworkedNodeContext { return; } - const renderSize = this.calculateRenderSizeOnLoad({ + const adjustedSize = NodeUtilities.adjustNodeSizeToCanvas({ componentSize: { width: this.videoComponent.videoWidth, height: this.videoComponent.videoHeight, }, maxSize: canvasSize, }); + const centerPosition = NodeUtilities.positionNodeOnCanvasCenter({ + canvasOffset: canvasPosition, + componentSize: adjustedSize, + maxSize: canvasSize, + }); this.kNode.image(this.videoComponent); - this.kNode.setSize(renderSize); + this.kNode.setSize(adjustedSize); + this.kNode.setPosition(centerPosition); this.videoComponent.currentTime = 0; // ensure it shows up on screen // it might have length here which we will need to trim down to 7 seconds. console.log(`Video Duration: ${this.videoComponent.duration}`); this.duration = this.videoComponent.duration; - this.kNode.fill(null); + this.kNode.fill(transparent); loadingBar.updateProgress(50); }; @@ -390,7 +403,7 @@ export class VideoNode extends NetworkedNodeContext { await this.updateImage(this.imageSources[this.imageIndex]); this.imageIndex = this.imageIndex + 1; - this.updateLoadingBarPosition(); + // this.updateLoadingBarPosition(); loadingBar.updateMessage("Generating"); diff --git a/src/KonvaApp/Nodes/constants.ts b/src/KonvaApp/Nodes/constants.ts new file mode 100644 index 0000000000..b5dfe351a1 --- /dev/null +++ b/src/KonvaApp/Nodes/constants.ts @@ -0,0 +1,7 @@ +export const transparent = "rgba(0,0,0,0)"; +export const primaryOrange = "rgba(230,100,98,1)"; +export const highlightStrokeWidth = 10; +export const minNodeSize = { + width: 200, + height: 200, +}; diff --git a/src/KonvaApp/Nodes/index.ts b/src/KonvaApp/Nodes/index.ts new file mode 100644 index 0000000000..bb5ec0f41e --- /dev/null +++ b/src/KonvaApp/Nodes/index.ts @@ -0,0 +1,3 @@ +export { ImageNode } from "./ImageNode"; +export { VideoNode } from "./VideoNode"; +export { TextNode } from "./TextNodes"; diff --git a/src/KonvaApp/NodesManagers/NodeTransformer.ts b/src/KonvaApp/NodesManagers/NodeTransformer.ts index 3227bbb11d..c5a1e37438 100644 --- a/src/KonvaApp/NodesManagers/NodeTransformer.ts +++ b/src/KonvaApp/NodesManagers/NodeTransformer.ts @@ -6,6 +6,7 @@ export class NodeTransformer { constructor() { this.kTransformer = new Konva.Transformer({ + padding: 0, anchorStyleFunc: (anchor) => { // anchor is Konva.Rect instance // you manually change its styling diff --git a/src/KonvaApp/NodesManagers/SelectionManager.ts b/src/KonvaApp/NodesManagers/SelectionManager.ts index 7026cfc60e..2083f6b5fa 100644 --- a/src/KonvaApp/NodesManagers/SelectionManager.ts +++ b/src/KonvaApp/NodesManagers/SelectionManager.ts @@ -2,6 +2,7 @@ import Konva from "konva"; import { NodeTransformer } from "./NodeTransformer"; import { MediaNode, Position, Transformation } from "../types"; import { uiAccess } from "~/signals"; +import { NetworkedNode } from "../Nodes/NetworkedNode"; export enum SelectionManagerEvents { NODES_TRANSLATIONS = "nodestranslation", @@ -14,8 +15,8 @@ export interface NodesTranslationEventDetails { } export interface NodeTransformationEventDetails { nodes: Set; - initialTransformations: Map; - finalTransformations: Map; + initialTransformations: Map; + finalTransformations: Map; } export class SelectionManager { @@ -23,7 +24,7 @@ export class SelectionManager { private mediaLayerRef: Konva.Layer; private nodeTransformerRef: NodeTransformer; private initialPositions: Map; - private initialTransformations: Map; + private initialTransformations: Map; public eventTarget: EventTarget; private _isDragging: boolean = false; @@ -36,12 +37,15 @@ export class SelectionManager { }) { this.selectedNodes = new Set(); this.initialPositions = new Map(); - this.initialTransformations = new Map(); + this.initialTransformations = new Map(); this.eventTarget = new EventTarget(); this.mediaLayerRef = mediaLayerRef; this.nodeTransformerRef = nodeTransformerRef; } - + // This lets us perfom operations on the selected node. + public getSelectedNodes(): Set { + return this.selectedNodes; + } public selectNodes(nodes: MediaNode[]) { const unlockedNodes = nodes.filter((node) => { return !node.isLocked(); @@ -60,11 +64,9 @@ export class SelectionManager { } node.highlight(); if (this.selectedNodes.size === 0) { - node.removeListenToBaseKNodeTransformations(); - node.listenToBaseKNodeTransformations(); - node.removeListenToBaseKNodeDrags(); - node.listenToBaseKNodeDrags(); - node.updateContextComponents(); + node.setIsKEventRef(true); + this.updateContextComponents(node); + this.showContextComponents(node); } this.selectedNodes.add(node); this.updateNodeTransformer(); @@ -75,9 +77,10 @@ export class SelectionManager { } public deselectNode(node: MediaNode, doNotDraw?: boolean): void { - node.unHighLight(); - node.removeListenToBaseKNodeTransformations(); - node.removeListenToBaseKNodeDrags(); + node.unhighlight(); + if (node.isKEventRef()) { + node.setIsKEventRef(false); + } this.selectedNodes.delete(node); this.updateNodeTransformer(); if (!doNotDraw) { @@ -88,16 +91,21 @@ export class SelectionManager { public clearSelection(): void { console.log("Clearing Selection"); this.selectedNodes.forEach((node) => { - node.unHighLight(); - node.removeListenToBaseKNodeTransformations(); - node.removeListenToBaseKNodeDrags(); + node.unhighlight(); + if (node.isKEventRef()) { + node.setIsKEventRef(false); + } }); this.selectedNodes.clear(); this.nodeTransformerRef.clear(); - uiAccess.toolbarNode.hide(); + this.hideContextComponents(); this.mediaLayerRef.batchDraw(); } + + public onToggleLock(node: MediaNode) { + this.updateContextComponents(node); + } public isDragging() { return this._isDragging; } @@ -108,13 +116,15 @@ export class SelectionManager { public dragStart() { // track the dragging state for mouseevents in nodes this._isDragging = true; + this.hideContextComponents(); + // track all nodes initial position when dragstart this.selectedNodes.forEach((selectedNode) => { this.initialPositions.set(selectedNode, selectedNode.kNode.position()); }); } - public dragEnd() { + public dragEnd(refNode: MediaNode) { // release the dragging state for mouseevents in nodes this._isDragging = false; // track and map the final positions @@ -135,36 +145,49 @@ export class SelectionManager { }, ), ); + // done, clear data and show menu this.initialPositions.clear(); + this.updateContextComponents(refNode); + this.showContextComponents(refNode); + } + private getKNodeTransformation(kNode: Konva.Node) { + return { + kNodeId: kNode._id, + position: kNode.position(), + size: kNode.size(), + rotation: kNode.rotation(), + scale: { + x: kNode.scaleX(), + y: kNode.scaleY(), + }, + }; } - public transformStart() { // track all nodes initial transformation when transformStart this.selectedNodes.forEach((selectedNode) => { - this.initialTransformations.set(selectedNode, { - position: selectedNode.kNode.position(), - size: selectedNode.kNode.size(), - rotation: selectedNode.kNode.rotation(), - scale: { - scaleX: selectedNode.kNode.scaleX(), - scaleY: selectedNode.kNode.scaleY(), - }, - }); + const transformations = [this.getKNodeTransformation(selectedNode.kNode)]; + if (selectedNode.kNode instanceof Konva.Group) { + const childKNodes = selectedNode.kNode.getChildren(); + childKNodes.forEach((childKNode) => { + transformations.push(this.getKNodeTransformation(childKNode)); + }); + } + this.initialTransformations.set(selectedNode, transformations); }); + this.hideContextComponents(); } - public transformEnd() { + public transformEnd(refNode: MediaNode) { // track and map the final positions - const finalTransformation = new Map(); + const finalTransformations = new Map(); this.selectedNodes.forEach((selectedNode) => { - finalTransformation.set(selectedNode, { - position: selectedNode.kNode.position(), - size: selectedNode.kNode.size(), - rotation: selectedNode.kNode.rotation(), - scale: { - scaleX: selectedNode.kNode.scaleX(), - scaleY: selectedNode.kNode.scaleY(), - }, - }); + const transformations = [this.getKNodeTransformation(selectedNode.kNode)]; + if (selectedNode.kNode instanceof Konva.Group) { + const childKNodes = selectedNode.kNode.getChildren(); + childKNodes.forEach((childKNode) => { + transformations.push(this.getKNodeTransformation(childKNode)); + }); + } + finalTransformations.set(selectedNode, transformations); }); // dispatch the info for engine to manage un-redo stack this.eventTarget.dispatchEvent( @@ -174,16 +197,13 @@ export class SelectionManager { detail: { nodes: this.selectedNodes, initialTransformations: new Map(this.initialTransformations), - finalTransformations: finalTransformation, + finalTransformations: finalTransformations, }, }, ), ); - } - - // This lets us perfom operations on the selected node. - public getSelectedNodes(): Set { - return this.selectedNodes; + this.updateContextComponents(refNode); + this.showContextComponents(refNode); } private updateNodeTransformer() { @@ -194,4 +214,66 @@ export class SelectionManager { ); this.nodeTransformerRef.enable({ selectedNodes: transformableNodes }); } + + protected calculateContextualsPosition(kNode: Konva.Node) { + const w = kNode.getSize().width * kNode.scaleX(); + const h = kNode.getSize().height * kNode.scaleY(); + const x0 = kNode.getPosition().x; + const y0 = kNode.getPosition().y; + + const d = kNode.getAbsoluteRotation(); + const r = d >= 0 ? (d * Math.PI) / 180 : ((360 + d) * Math.PI) / 180; + + let px: number, py: number; + if (r < Math.PI / 2) { + px = x0 + (h * Math.sin(r) + w * Math.cos(r)) / 2 - h * Math.sin(r); + py = y0 + h * Math.cos(r) + w * Math.cos(Math.PI / 2 - r); + } else if (r < Math.PI) { + px = x0 - (h * Math.sin(r) - w * Math.cos(r)) / 2; + py = y0 + w * Math.cos(Math.PI / 2 - r); + } else if (r < (Math.PI * 3) / 2) { + px = x0 + h * Math.cos(r) - (h * Math.cos(r) + w * Math.sin(r)) / 2; + py = y0; + } else { + px = x0 + (-h * Math.sin(r) + w * Math.cos(r)) / 2; + py = y0 + h * Math.cos(r); + } + return { x: px, y: py }; + } + + public hideContextComponents() { + uiAccess.toolbarNode.hide(); + } + public showContextComponents(node: MediaNode) { + if (!uiAccess.toolbarNode.isShowing()) { + uiAccess.toolbarNode.show({ + locked: node.isLocked(), + }); + } else { + uiAccess.toolbarNode.update({ + locked: node.isLocked(), + }); + } + if (node instanceof NetworkedNode) { + if (!node.didFinishLoading && !uiAccess.loadingBar.isShowing()) { + uiAccess.loadingBar.show(); + } + } + } + public updateContextComponents(node: MediaNode) { + const coord = this.calculateContextualsPosition(node.kNode); + if (node.isLocked() !== uiAccess.toolbarNode.isLocked()) { + uiAccess.toolbarNode.setLocked(node.isLocked()); + } + uiAccess.toolbarNode.setPosition({ + x: coord.x, + y: coord.y, + }); + if (node instanceof NetworkedNode) { + uiAccess.loadingBar.updatePosition({ + x: coord.x, + y: coord.y, + }); + } + } } diff --git a/src/KonvaApp/NodesManagers/SelectorSquare.ts b/src/KonvaApp/NodesManagers/SelectorSquare.ts index 5d0fa8b0a5..740284e5ce 100644 --- a/src/KonvaApp/NodesManagers/SelectorSquare.ts +++ b/src/KonvaApp/NodesManagers/SelectorSquare.ts @@ -33,19 +33,21 @@ export class SelectorSquare { } public enable({ captureCanvasRef, + mediaLayerRef, nodesManagerRef, selectionManagerRef, - stage, + stageRef, }: { captureCanvasRef: Konva.Rect; + mediaLayerRef: Konva.Layer; nodesManagerRef: NodesManager; selectionManagerRef: SelectionManager; - stage: Konva.Stage; + stageRef: Konva.Stage; }) { - stage.on("mousedown touchstart", (e) => { - const stagePointerPos = stage.getPointerPosition(); + stageRef.on("mousedown touchstart", (e) => { + const stagePointerPos = stageRef.getPointerPosition(); if ( - (e.target !== stage && e.target !== captureCanvasRef) || // do nothing if we mousedown on any shape + (e.target !== stageRef && e.target !== captureCanvasRef) || // do nothing if we mousedown on any shape stagePointerPos === null || // do nothing if pointers not available e.evt.shiftKey // do nothing so then multiselect is more forgiving in misclicks ) { @@ -54,7 +56,7 @@ export class SelectorSquare { // start handle mousedown e.evt.preventDefault(); - if (e.target === stage || e.target === captureCanvasRef) { + if (e.target === stageRef || e.target === captureCanvasRef) { //moused down on empty space, clear previous selection first selectionManagerRef.clearSelection(); } @@ -73,9 +75,9 @@ export class SelectorSquare { this.selecting = true; }); - stage.on("mousemove touchmove", (e) => { + stageRef.on("mousemove touchmove", (e) => { // do nothing if we didn't start selection - const stagePointerPos = stage.getPointerPosition(); + const stagePointerPos = stageRef.getPointerPosition(); if (!this.selecting || stagePointerPos === null) { return; } @@ -97,7 +99,7 @@ export class SelectorSquare { height: Math.abs(y2 - y1), }); - stage.on("mouseup touchend", (e) => { + stageRef.on("mouseup touchend", (e) => { // do nothing if we didn't start selection this.selecting = false; if (!this.kSquare.visible()) { @@ -107,7 +109,7 @@ export class SelectorSquare { // update visibility this.kSquare.visible(false); // Find all the Nodes and feed them into Selectmanager - var shapes = stage.find("Image"); + var shapes = mediaLayerRef.getChildren(); var box = this.kSquare.getClientRect(); var foundKNodes = shapes.filter((shape) => Konva.Util.haveIntersection(box, shape.getClientRect()), diff --git a/src/KonvaApp/RenderingPrimitives/RenderEngine.ts b/src/KonvaApp/RenderingPrimitives/RenderEngine.ts index 68e13db6da..1a9a4ce34c 100644 --- a/src/KonvaApp/RenderingPrimitives/RenderEngine.ts +++ b/src/KonvaApp/RenderingPrimitives/RenderEngine.ts @@ -206,10 +206,11 @@ export class RenderEngine { } // update the context menu - this.videoNodes.forEach((node) => { - // find selected node... TODO and update that position - node.updateContextComponents(); - }); + // TODO: find selected node and update that position + // this.videoNodes.forEach((node) => { + // node.updateContextComponents(); + // }); + this.mediaLayerRef.batchDraw(); } @@ -350,7 +351,7 @@ export class RenderEngine { return; } item.kNode.listening(false); - item.unHighLight(); + item.unhighlight(); if (item.didFinishLoading == false) { // error out and show error message //this.startProcessing(); diff --git a/src/KonvaApp/UndoRedo/TransformCommand.ts b/src/KonvaApp/UndoRedo/TransformCommand.ts index 20692917ae..d9d1f6b865 100644 --- a/src/KonvaApp/UndoRedo/TransformCommand.ts +++ b/src/KonvaApp/UndoRedo/TransformCommand.ts @@ -4,8 +4,8 @@ import { MediaNode, Transformation } from "../types"; export class TransformCommand implements ICommand { private nodes: Set; - private initialTransformations: Map; - private finalTransformations: Map; + private initialTransformations: Map; + private finalTransformations: Map; private layerRef: Konva.Layer; constructor({ @@ -15,24 +15,54 @@ export class TransformCommand implements ICommand { layerRef, }: { nodes: Set; - initialTransformations: Map; - finalTransformations: Map; + initialTransformations: Map; + finalTransformations: Map; layerRef: Konva.Layer; }) { this.nodes = new Set(nodes); this.initialTransformations = initialTransformations; this.finalTransformations = finalTransformations; this.layerRef = layerRef; + console.log("transform command", this); } + private transformKNode(kNode: Konva.Node, transform: Transformation) { + kNode.setAttrs({ + position: transform.position, + size: transform.size, + rotation: transform.rotation, + scale: transform.scale, + }); + } + private findKNodeOfTransform = ( + node: MediaNode, + transform: Transformation, + ) => { + const parentKNode = node.kNode; + if (parentKNode._id === transform.kNodeId) { + this.transformKNode(parentKNode, transform); + return; + } + if (parentKNode instanceof Konva.Group) { + const childKNode = parentKNode + .getChildren() + .find((currChild) => currChild._id === transform.kNodeId); + if (childKNode) { + this.transformKNode(childKNode, transform); + return; + } + } + if (import.meta.env.DEV) { + console.warn("Error in Transform", node, transform); + } + }; + execute() { this.nodes.forEach((node) => { - const finalTransformation = this.finalTransformations.get(node); - if (finalTransformation) { - node.kNode.position(finalTransformation.position); - node.kNode.size(finalTransformation.size); - node.kNode.rotation(finalTransformation.rotation); - node.kNode.scaleX(finalTransformation.scale.scaleX); - node.kNode.scaleY(finalTransformation.scale.scaleY); + const finalTransformations = this.finalTransformations.get(node); + if (finalTransformations) { + finalTransformations.forEach((transform) => + this.findKNodeOfTransform(node, transform), + ); } }); this.layerRef.draw(); @@ -40,13 +70,11 @@ export class TransformCommand implements ICommand { undo() { this.nodes.forEach((node) => { - const initialTransformation = this.initialTransformations.get(node); - if (initialTransformation) { - node.kNode.position(initialTransformation.position); - node.kNode.size(initialTransformation.size); - node.kNode.rotation(initialTransformation.rotation); - node.kNode.scaleX(initialTransformation.scale.scaleX); - node.kNode.scaleY(initialTransformation.scale.scaleY); + const initialTransformations = this.initialTransformations.get(node); + if (initialTransformations) { + initialTransformations.forEach((transform) => + this.findKNodeOfTransform(node, transform), + ); } }); this.layerRef.draw(); diff --git a/src/KonvaApp/types.ts b/src/KonvaApp/types.ts index ff49247e9f..d01e32ee77 100644 --- a/src/KonvaApp/types.ts +++ b/src/KonvaApp/types.ts @@ -1,21 +1,27 @@ +import { Vector2d } from "konva/lib/types"; +import { BaseNode } from "./Nodes/BaseNode"; +// import { NetworkedNode } from "./Nodes/NetworkedNode"; import { VideoNode } from "./Nodes/VideoNode"; import { ImageNode } from "./Nodes/ImageNode"; -import { NetworkedNodeContext } from "./Nodes/NetworkedNodeContext"; -export type MediaNode = NetworkedNodeContext | VideoNode | ImageNode; +import { TextNode } from "./Nodes/TextNodes"; -export interface Scale { - scaleX: number; - scaleY: number; +export interface TextNodeData { + text: string; } + +export type MediaNode = + // | NetworkedNode + BaseNode | VideoNode | ImageNode | TextNode; + +export interface Position extends Vector2d {} +export interface Scale extends Vector2d {} export interface Size { width: number; height: number; } -export interface Position { - x: number; - y: number; -} + export interface Transformation { + kNodeId: number | string; position: Position; size: Size; scale: Scale; diff --git a/src/KonvaRootComponent/KonvaRootComponent.tsx b/src/KonvaRootComponent/KonvaRootComponent.tsx index 1396910b11..3754c8baa0 100644 --- a/src/KonvaRootComponent/KonvaRootComponent.tsx +++ b/src/KonvaRootComponent/KonvaRootComponent.tsx @@ -48,9 +48,7 @@ export const KonvaRootComponent = ({ className }: { className: string }) => { /> { return ( <> + {/* Dialogs that are opened from within Reactland*/} + { + dispatchUiEvents.addTextToEngine(data); + }} + closeCallback={resetAll} + /> + {/* Dialogs that are opened from the Engine*/} diff --git a/src/KonvaRootComponent/SignaledToolbarMain.tsx b/src/KonvaRootComponent/SignaledToolbarMain.tsx index 98db69b1bc..7d30caf134 100644 --- a/src/KonvaRootComponent/SignaledToolbarMain.tsx +++ b/src/KonvaRootComponent/SignaledToolbarMain.tsx @@ -9,17 +9,14 @@ import { LoadingBar } from "~/components/ui"; import { ToolbarMainButtonNames } from "~/components/features/ToolbarMain/enum"; import { LayoutSignalType } from "./contextSignals/layout"; +import { AppUiContextInterface } from "./contextSignals/appUi"; export const SignaledToolbarMain = ({ layoutSignal, - openAddImage, - openAddVideo, - openAIStylize, + appUiContext, }: { layoutSignal: LayoutSignalType; - openAddImage: () => void; - openAddVideo: () => void; - openAIStylize: () => void; + appUiContext: AppUiContextInterface; }) => { //// for testing const { isMobile } = layoutSignal; @@ -34,12 +31,26 @@ export const SignaledToolbarMain = ({ /// end for testing const loadingBar = toolbarMain.loadingBar.signal.value; + const getButtonDispatcher = (buttonName: ToolbarMainButtonNames) => { + switch (buttonName) { + case ToolbarMainButtonNames.ADD_TEXT: + return () => appUiContext.openEditText(); + case ToolbarMainButtonNames.AI_STYLIZE: + return () => appUiContext.openAIStylize(); + case ToolbarMainButtonNames.ADD_IMAGE: + return () => appUiContext.openAddImage(); + case ToolbarMainButtonNames.ADD_VIDEO: + return () => appUiContext.openAddVideo(); + default: + return dispatchers[buttonName]; + } + }; const buttonProps = Object.values(ToolbarMainButtonNames).reduce( (acc, buttonName) => { acc[buttonName] = { disabled: toolbarMain.signal.value.buttonStates[buttonName].disabled, active: toolbarMain.signal.value.buttonStates[buttonName].active, - onClick: dispatchers[buttonName], + onClick: getButtonDispatcher(buttonName), }; return acc; }, @@ -74,9 +85,6 @@ export const SignaledToolbarMain = ({ openAddImage()} - openAddVideo={() => openAddVideo()} - openAIStylize={() => openAIStylize()} /> ); diff --git a/src/KonvaRootComponent/contextSignals/appUi.ts b/src/KonvaRootComponent/contextSignals/appUi.ts index 3312501677..762ffa6565 100644 --- a/src/KonvaRootComponent/contextSignals/appUi.ts +++ b/src/KonvaRootComponent/contextSignals/appUi.ts @@ -1,12 +1,20 @@ import { useCallback, useRef } from "react"; import { signal } from "@preact/signals-react"; +export interface AppUiContextInterface { + resetAll: () => void; + openAddImage: (file?: File) => void; + openAddVideo: (file?: File) => void; + openAIStylize: () => void; + openEditText: () => void; +} export type AppUiSignalType = { isAddVideoOpen: boolean; stagedVideo: File | null; isAddImageOpen: boolean; stagedImage: File | null; isAiStylizeOpen: boolean; + isEditTextOpen: boolean; }; const appUiInitialState = { isAddVideoOpen: false, @@ -14,6 +22,7 @@ const appUiInitialState = { isAddImageOpen: false, stagedImage: null, isAiStylizeOpen: false, + isEditTextOpen: false, }; export const useAppUiContext = () => { const appUiRef = useRef(signal(appUiInitialState)); @@ -42,11 +51,18 @@ export const useAppUiContext = () => { isAiStylizeOpen: true, }; }, []); + const openEditText = useCallback(() => { + appUiSignal.value = { + ...appUiInitialState, + isEditTextOpen: true, + }; + }, []); return { signal: appUiSignal, resetAll, openAddImage, openAddVideo, openAIStylize, + openEditText, }; }; diff --git a/src/components/features/DialogAiStylize/DialogAiStylize.tsx b/src/components/features/DialogAiStylize/DialogAiStylize.tsx index 939dd55d89..7bc2013973 100644 --- a/src/components/features/DialogAiStylize/DialogAiStylize.tsx +++ b/src/components/features/DialogAiStylize/DialogAiStylize.tsx @@ -86,9 +86,7 @@ export const DialogAiStylize = ({ > {panelState === SubPanelNames.BASIC && ( <> - - Use AI to Stylize{" "} - + Use AI to Stylize void; + onDoneEditText: (doneData: TextNodeData) => void; +}) => { + const [text, setText] = useState(""); + const [textFormatData, setTextFormatData] = useState({ + color: "#000000", + fontFamily: "Arial", + fontSize: 20, + fontStyle: FontStyle.NORMAL, + fontWeight: FontWeight.NORMAL, + textAlign: TextAlign.CENTER, + textDecoration: TextDecoration.NONE, + }); + + const handleOnChangeText = (newText: string) => { + setText(newText); + }; + const handleOnChangeFormatting = (newFormatData: Partial) => { + setTextFormatData((curr) => ({ + ...curr, + ...newFormatData, + })); + }; + const handleOnDoneEditText = () => { + const textNodeData = { + text: text, + fill: textFormatData.color, + fontFamily: textFormatData.fontFamily, + align: textFormatData.textAlign, + fontStyle: + textFormatData.fontStyle === FontStyle.NORMAL && + textFormatData.fontWeight === FontWeight.NORMAL + ? "normal" + : `${textFormatData.fontWeight !== FontWeight.NORMAL && textFormatData.fontWeight} ${textFormatData.fontStyle !== FontStyle.NORMAL && textFormatData.fontStyle}`, + textDecoration: + textFormatData.textDecoration === TextDecoration.NONE + ? "" + : textFormatData.textDecoration, + }; + onDoneEditText(textNodeData); + closeCallback(); + }; + return ( + +
+ + Edit Text + +
+ + + +
+
+
+
+ ); +}; diff --git a/src/components/features/DialogEditText/index.ts b/src/components/features/DialogEditText/index.ts new file mode 100644 index 0000000000..6ac727535e --- /dev/null +++ b/src/components/features/DialogEditText/index.ts @@ -0,0 +1 @@ +export { DialogEditText } from "./DialogEditText"; diff --git a/src/components/features/ToolbarMain/ToolbarMain.tsx b/src/components/features/ToolbarMain/ToolbarMain.tsx new file mode 100644 index 0000000000..68792b8763 --- /dev/null +++ b/src/components/features/ToolbarMain/ToolbarMain.tsx @@ -0,0 +1,128 @@ +import { Fragment, MouseEventHandler } from "react"; +import { twMerge } from "tailwind-merge"; +import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { + faArrowRotateLeft, + faArrowRotateRight, + faBars, + faCameraRotate, + faDownload, + faFilePlus, + faFilm, + faFloppyDisk, + faHatWizard, + faImage, + faLocationArrow, + faText, +} from "@fortawesome/pro-solid-svg-icons"; + +import { ToolbarButton } from "../ToolbarButton"; + +// style and constants +import { paperWrapperStyles, toolTipStyles } from "~/components/styles"; +import { ToolbarMainButtonNames } from "./enum"; + +export const ToolbarMain = ({ + disabled = false, + buttonProps, +}: { + disabled?: boolean; + buttonProps: { + [key in ToolbarMainButtonNames]: { + disabled?: boolean; + active?: boolean; + onClick?: MouseEventHandler; + }; + }; +}) => { + return ( + <> +
+
+ +
+
+ + + + + + + + + Add Image + + + Add Video + + + + {/* */} + +
+
+ + + + +
+
+ + ); +}; diff --git a/src/components/features/ToolbarMain/enum.ts b/src/components/features/ToolbarMain/enum.ts index 81866cbc32..f6387cab39 100644 --- a/src/components/features/ToolbarMain/enum.ts +++ b/src/components/features/ToolbarMain/enum.ts @@ -1,7 +1,10 @@ export enum ToolbarMainButtonNames { MENU = "MENU", SELECT = "SELECT", - CHANGE_CAMERA_ORIENTATION = "CHANGE_CAMERA_ORIENTATION", + ADD_TEXT = "ADD_TEXT", + ADD_IMAGE = "ADD_IMAGE", + ADD_VIDEO = "ADD_VIDEO", + // CHANGE_CAMERA_ORIENTATION = "CHANGE_CAMERA_ORIENTATION", AI_STYLIZE = "AI_STYLIZE", UNDO = "UNDO", REDO = "REDO", diff --git a/src/components/features/ToolbarMain/index.tsx b/src/components/features/ToolbarMain/index.tsx index 08c116bf8a..499b972e8f 100644 --- a/src/components/features/ToolbarMain/index.tsx +++ b/src/components/features/ToolbarMain/index.tsx @@ -1,129 +1 @@ -import { Fragment, MouseEventHandler } from "react"; -import { twMerge } from "tailwind-merge"; -import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faArrowRotateLeft, - faArrowRotateRight, - faBars, - faCameraRotate, - faDownload, - faFilePlus, - faFilm, - faFloppyDisk, - faHatWizard, - faImage, - faLocationArrow, -} from "@fortawesome/pro-solid-svg-icons"; - -import { ToolbarButton } from "../ToolbarButton"; - -// style and constants -import { paperWrapperStyles, toolTipStyles } from "~/components/styles"; -import { ToolbarMainButtonNames } from "./enum"; - -export const ToolbarMain = ({ - disabled = false, - buttonProps, - openAddImage, - openAddVideo, - openAIStylize, -}: { - disabled?: boolean; - buttonProps: { - [key in ToolbarMainButtonNames]: { - disabled?: boolean; - active?: boolean; - onClick?: MouseEventHandler; - }; - }; - openAddImage: () => void; - openAddVideo: () => void; - openAIStylize: () => void; -}) => { - return ( - <> -
-
- -
-
- - - - - - - - Add Image - - - Add Video - - - - - -
-
- - - - -
-
- - ); -}; +export { ToolbarMain } from "./ToolbarMain"; diff --git a/src/components/features/index.ts b/src/components/features/index.ts index 9ac1f1fac8..3de4af3caa 100644 --- a/src/components/features/index.ts +++ b/src/components/features/index.ts @@ -3,6 +3,7 @@ export { DialogAddImage } from "./DialogAddImage"; export { DialogAddVideo } from "./DialogAddVideo"; export { DialogAiStylize } from "./DialogAiStylize"; export { DialogChromakey } from "./DialogChromakey"; +export { DialogEditText } from "./DialogEditText"; export { DialogError } from "./DialogError"; export { ProfileDropdown } from "./ToolbarUserProfile/ProfileDropdown"; export { ToolbarMain } from "./ToolbarMain"; diff --git a/src/components/ui/ComboBox.tsx b/src/components/ui/ComboBox.tsx new file mode 100644 index 0000000000..84be5b3638 --- /dev/null +++ b/src/components/ui/ComboBox.tsx @@ -0,0 +1,58 @@ +import { useState } from "react"; +import { faChevronDown } from "@fortawesome/pro-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { + Combobox as HeadlessComboBox, + ComboboxButton, + ComboboxInput, + ComboboxOption, + ComboboxOptions, +} from "@headlessui/react"; + +interface ComboBoxInterface { + options: string[]; + value: string; + onChange: (newVal: string) => void; +} +export const Combobox = ({ options, value, onChange }: ComboBoxInterface) => { + const [query, setQuery] = useState(""); + const filteredOptions = + query === "" + ? options + : options.filter((option) => { + return option.toLowerCase().includes(option.toLowerCase()); + }); + + return ( + setQuery("")} + > +
+ font} + onChange={(event) => setQuery(event.target.value)} + /> + + + +
+ + {filteredOptions.map((option, idx) => ( + + {option} + + ))} + +
+ ); +}; diff --git a/src/components/ui/TextArea.tsx b/src/components/ui/TextArea.tsx index 1e3f41937d..8ded7eaf44 100644 --- a/src/components/ui/TextArea.tsx +++ b/src/components/ui/TextArea.tsx @@ -2,7 +2,7 @@ import { TextareaHTMLAttributes } from "react"; import { twMerge } from "tailwind-merge"; import { kebabCase } from "~/utilities"; -type ResizeType = +export type ResizeType = | "none" | "both" | "horizontal" @@ -11,16 +11,18 @@ type ResizeType = | "inline" | undefined; +export interface TextareaInterface + extends TextareaHTMLAttributes { + label?: string; + resize?: ResizeType; +} export const Textarea = ({ className, label, resize = "vertical", id, ...rest -}: TextareaHTMLAttributes & { - label?: string; - resize?: ResizeType; -}) => { +}: TextareaInterface) => { return (
{label && } diff --git a/src/components/ui/TextEditor/ButtonTextStyles.tsx b/src/components/ui/TextEditor/ButtonTextStyles.tsx new file mode 100644 index 0000000000..e9994b2de2 --- /dev/null +++ b/src/components/ui/TextEditor/ButtonTextStyles.tsx @@ -0,0 +1,85 @@ +import { + faBold, + faItalic, + faStrikethrough, + faUnderline, +} from "@fortawesome/pro-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { buttonGroupButton, buttonGroupGrouper } from "./styles"; +import { FontStyle, FontWeight, TextDecoration } from "./types"; +export const ButtonsTextStyles = ({ + fontStyle: currStyle, + fontWeight: currWeight, + textDecoration: currDecoration, + onChangeFontStyle, + onChangeFontWeight, + onChangeTextDecoration, +}: { + fontStyle: FontStyle; + fontWeight: FontWeight; + textDecoration: TextDecoration; + onChangeFontStyle: (newVal: FontStyle) => void; + onChangeFontWeight: (newVal: FontWeight) => void; + onChangeTextDecoration: (newVal: TextDecoration) => void; +}) => { + const isBold = currWeight === FontWeight.BOLD ? true : undefined; + const isItalic = currStyle === FontStyle.ITALIC ? true : undefined; + const isSelectedDecoration = (buttonVal: TextDecoration) => + currDecoration === buttonVal ? true : undefined; + + const toggleBold = () => { + if (currWeight === FontWeight.BOLD) { + onChangeFontWeight(FontWeight.NORMAL); + } else { + onChangeFontWeight(FontWeight.BOLD); + } + }; + const handleOnChangeFontStyle = (selected: FontStyle) => { + if (selected === currStyle) { + onChangeFontStyle(FontStyle.NORMAL); + } else { + onChangeFontStyle(selected); + } + }; + const handleOnChangeTextDecoration = (selected: TextDecoration) => { + if (selected === currDecoration) { + onChangeTextDecoration(TextDecoration.NONE); + } else { + onChangeTextDecoration(selected); + } + }; + return ( +
+ + + + +
+ ); +}; diff --git a/src/components/ui/TextEditor/ButtonsAlignment.tsx b/src/components/ui/TextEditor/ButtonsAlignment.tsx new file mode 100644 index 0000000000..dd64993d0b --- /dev/null +++ b/src/components/ui/TextEditor/ButtonsAlignment.tsx @@ -0,0 +1,51 @@ +import { + faAlignCenter, + faAlignJustify, + faAlignLeft, + faAlignRight, +} from "@fortawesome/pro-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { TextAlign } from "./types"; +import { buttonGroupButton, buttonGroupGrouper } from "./styles"; +export const ButtonsAlignments = ({ + value, + onChange, +}: { + value: TextAlign; + onChange: (newVal: TextAlign) => void; +}) => { + const isSelected = (alignment: TextAlign) => + value === alignment ? true : undefined; + return ( +
+ + + + +
+ ); +}; diff --git a/src/components/ui/TextEditor/TextEditor.tsx b/src/components/ui/TextEditor/TextEditor.tsx new file mode 100644 index 0000000000..a62795e9cd --- /dev/null +++ b/src/components/ui/TextEditor/TextEditor.tsx @@ -0,0 +1,139 @@ +import { ChangeEvent } from "react"; +import { faFont } from "@fortawesome/pro-solid-svg-icons"; +import { + CloseButton, + Popover, + PopoverButton, + PopoverPanel, +} from "@headlessui/react"; +import { HexColorPicker } from "react-colorful"; + +import { Textarea, TextareaInterface, ResizeType } from "../TextArea"; +import { InputNumber } from "../InputNumber"; +import { Combobox } from "../ComboBox"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ButtonsAlignments } from "./ButtonsAlignment"; +import { ButtonsTextStyles } from "./ButtonTextStyles"; + +import { + TextFormatData, + TextAlign, + TextDecoration, + FontStyle, + FontWeight, +} from "./types"; + +const FontFamilies = ["Arial", "Helvetica", "Times New Roman", "Comic Sans MS"]; + +export const TextEditor = ({ + text, + formatData, + onChangeText, + onChangeFormatting, + TextareaProps, +}: { + text: string; + formatData: TextFormatData; + onChangeText: (newText: string) => void; + onChangeFormatting: (newFormatData: Partial) => void; + TextareaProps?: TextareaInterface; +}) => { + const handleOnChangeText = (e: ChangeEvent) => { + onChangeText(e.target.value); + }; + const onChangeTextAlignment = (newAlignment: TextAlign) => { + onChangeFormatting({ + textAlign: newAlignment, + }); + }; + const onChangeFontStyle = (newStyle: FontStyle) => { + onChangeFormatting({ + fontStyle: newStyle, + }); + }; + const onChangeFontWeight = (newWeight: FontWeight) => { + onChangeFormatting({ + fontWeight: newWeight, + }); + }; + const onChangeTextDecoration = (newDecor: TextDecoration) => { + onChangeFormatting({ + textDecoration: newDecor, + }); + }; + const onChangeFontFamily = (newFontFamily: string) => { + onChangeFormatting({ + fontFamily: newFontFamily, + }); + }; + const onChangeTextColor = (newTextColor: string) => { + onChangeFormatting({ + color: newTextColor, + }); + }; + const onChangeFontSize = (newVal: number) => { + onChangeFormatting({ + fontSize: newVal, + }); + }; + + const TextAreaStates = { + style: { + ...formatData, + width: "500px", + }, + rows: 7, + placeholder: "...", + resize: "none" as ResizeType, + onChange: handleOnChangeText, + value: text, + }; + const unionTextAreaProps = { + ...TextareaProps, + ...TextAreaStates, + }; + return ( +
+
+ + + + + + + + +

{formatData.color}

+ OK +
+
+ +
+
+ + +
+