From 19d58843d7d616b21a348b3ee4e0115f95d36d80 Mon Sep 17 00:00:00 2001 From: wil Date: Mon, 7 Oct 2024 21:56:58 -0400 Subject: [PATCH] Feature/ add text (#44) * can add text retain stroke width while transforming is now possible reorganize how the inheritance of node classes, now text can be selected, multi-selected, translate and transform, all while working with the unredo stack organize toolbarmain and dialogues, now add text brings up a dialog, meant for accepting user text input finished plumbing the data from user text input to adding text to baord clean up make the dialog less weirdly vertical clean up, and place node in the center of canvas block adding text with no text, make textbox wider transformation of old node works with new transformation setup undo redo of transformation also works for text nodes now cleanup geometry fix almost done text editor ui text formatting from text editor is sent to engine and used on text nodes * rid of useless button --- package-lock.json | 618 ++---------------- package.json | 1 + src/KonvaApp/Engine.ts | 158 +++-- src/KonvaApp/Nodes/BaseNode.ts | 195 ++++++ src/KonvaApp/Nodes/ImageNode.ts | 40 +- src/KonvaApp/Nodes/NetworkedNode.ts | 26 + src/KonvaApp/Nodes/NetworkedNodeContext.ts | 252 ------- src/KonvaApp/Nodes/NodeUtilities.ts | 55 ++ src/KonvaApp/Nodes/TextNodes.ts | 98 +++ src/KonvaApp/Nodes/VideoNode.ts | 41 +- src/KonvaApp/Nodes/constants.ts | 7 + src/KonvaApp/Nodes/index.ts | 3 + src/KonvaApp/NodesManagers/NodeTransformer.ts | 1 + .../NodesManagers/SelectionManager.ts | 172 +++-- src/KonvaApp/NodesManagers/SelectorSquare.ts | 22 +- .../RenderingPrimitives/RenderEngine.ts | 11 +- src/KonvaApp/UndoRedo/TransformCommand.ts | 64 +- src/KonvaApp/types.ts | 24 +- src/KonvaRootComponent/KonvaRootComponent.tsx | 4 +- src/KonvaRootComponent/SignaledDialogs.tsx | 12 +- .../SignaledToolbarMain.tsx | 28 +- .../contextSignals/appUi.ts | 16 + .../DialogAiStylize/DialogAiStylize.tsx | 4 +- .../DialogEditText/DialogEditText.tsx | 102 +++ .../features/DialogEditText/index.ts | 1 + .../features/ToolbarMain/ToolbarMain.tsx | 128 ++++ src/components/features/ToolbarMain/enum.ts | 5 +- src/components/features/ToolbarMain/index.tsx | 130 +--- src/components/features/index.ts | 1 + src/components/ui/ComboBox.tsx | 58 ++ src/components/ui/TextArea.tsx | 12 +- .../ui/TextEditor/ButtonTextStyles.tsx | 85 +++ .../ui/TextEditor/ButtonsAlignment.tsx | 51 ++ src/components/ui/TextEditor/TextEditor.tsx | 139 ++++ src/components/ui/TextEditor/index.ts | 1 + src/components/ui/TextEditor/styles.ts | 4 + src/components/ui/TextEditor/types.ts | 28 + src/components/ui/index.ts | 1 + src/signals/uiAccess/toolbarNode.ts | 21 +- src/signals/uiEvents/addMedia.ts | 21 +- yarn.lock | 361 ++++------ 41 files changed, 1579 insertions(+), 1422 deletions(-) create mode 100644 src/KonvaApp/Nodes/BaseNode.ts create mode 100644 src/KonvaApp/Nodes/NetworkedNode.ts delete mode 100644 src/KonvaApp/Nodes/NetworkedNodeContext.ts create mode 100644 src/KonvaApp/Nodes/NodeUtilities.ts create mode 100644 src/KonvaApp/Nodes/TextNodes.ts create mode 100644 src/KonvaApp/Nodes/constants.ts create mode 100644 src/KonvaApp/Nodes/index.ts create mode 100644 src/components/features/DialogEditText/DialogEditText.tsx create mode 100644 src/components/features/DialogEditText/index.ts create mode 100644 src/components/features/ToolbarMain/ToolbarMain.tsx create mode 100644 src/components/ui/ComboBox.tsx create mode 100644 src/components/ui/TextEditor/ButtonTextStyles.tsx create mode 100644 src/components/ui/TextEditor/ButtonsAlignment.tsx create mode 100644 src/components/ui/TextEditor/TextEditor.tsx create mode 100644 src/components/ui/TextEditor/index.ts create mode 100644 src/components/ui/TextEditor/styles.ts create mode 100644 src/components/ui/TextEditor/types.ts 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 +
+
+ +
+
+ + +
+