diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 10f6886..1de6d40 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,14 +4,14 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
@@ -309,19 +309,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -378,9 +365,19 @@
+
+
+
+
+
+
+
+
+
+
+
-
@@ -388,10 +385,10 @@
+
-
@@ -498,22 +495,6 @@
-
-
- 1741548044897
-
-
-
- 1741548044897
-
-
-
- 1741568170877
-
-
-
- 1741568170877
-
1741580004784
@@ -890,7 +871,23 @@
1752023182341
-
+
+
+ 1752023796004
+
+
+
+ 1752023796004
+
+
+
+ 1752026153328
+
+
+
+ 1752026153328
+
+
@@ -937,8 +934,6 @@
-
-
@@ -962,7 +957,9 @@
-
+
+
+
false
diff --git a/package-lock.json b/package-lock.json
index 91db444..53ca6d1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -48,8 +48,10 @@
"rc-slider": "^11.1.8",
"react": "^18.3.1",
"react-dom": "^18.3.1",
+ "react-filerobot-image-editor": "^4.9.1",
"react-helmet": "^6.1.0",
"react-image-crop": "^11.0.7",
+ "react-konva": "^18.2.10",
"react-router-dom": "^6.23.1",
"tesseract.js": "^6.0.0",
"type-fest": "^4.35.0",
@@ -2906,6 +2908,45 @@
"win32"
]
},
+ "node_modules/@scaleflex/icons": {
+ "version": "2.10.27",
+ "resolved": "https://registry.npmjs.org/@scaleflex/icons/-/icons-2.10.27.tgz",
+ "integrity": "sha512-3E/tqXQrsuFIeGwDHE/ANEdDCPCYrt3ETk3/Q83M5ZZaFWdFWJG3bMeVBwNP2Nuul5OMr70LH3ce3krEObz98g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.0.0",
+ "@types/react-dom": ">=16.0.0",
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/@scaleflex/ui": {
+ "version": "2.10.27",
+ "resolved": "https://registry.npmjs.org/@scaleflex/ui/-/ui-2.10.27.tgz",
+ "integrity": "sha512-Id9EJjS4NWGn9V0pZRCk8YpM2PVEK8/a/BtTbgEW5L7wPI/APmZ9vGtCTM3HyTEBrfnvWmDlb0T5CfpozywKyA==",
+ "license": "MIT",
+ "dependencies": {
+ "@popperjs/core": "^2.6.0",
+ "@scaleflex/icons": "^2.10.27",
+ "@tippyjs/react": "^4.2.6",
+ "@types/lodash.merge": "^4.6.9",
+ "lodash.merge": "^4.6.2",
+ "prop-types": "^15.7.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.0.0",
+ "@types/react-dom": ">=16.0.0",
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0",
+ "styled-components": ">=5.0.0"
+ }
+ },
"node_modules/@sideway/address": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
@@ -3259,6 +3300,19 @@
"react-dom": "^18.0.0"
}
},
+ "node_modules/@tippyjs/react": {
+ "version": "4.2.6",
+ "resolved": "https://registry.npmjs.org/@tippyjs/react/-/react-4.2.6.tgz",
+ "integrity": "sha512-91RicDR+H7oDSyPycI13q3b7o4O60wa2oRbjlz2fyRLmHImc4vyDwuUP8NtZaN0VARJY5hybvDYrFzhY9+Lbyw==",
+ "license": "MIT",
+ "dependencies": {
+ "tippy.js": "^6.3.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
"node_modules/@tokenizer/token": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
@@ -3346,6 +3400,15 @@
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.5.tgz",
"integrity": "sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw=="
},
+ "node_modules/@types/lodash.merge": {
+ "version": "4.6.9",
+ "resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.9.tgz",
+ "integrity": "sha512-23sHDPmzd59kUgWyKGiOMO2Qb9YtqRO/x4IhkgNUiPQ1+5MUVqi6bCZeq9nBJ17msjIMbEIO5u+XW4Kz6aGUhQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/lodash": "*"
+ }
+ },
"node_modules/@types/morsee": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@types/morsee/-/morsee-1.0.2.tgz",
@@ -3397,7 +3460,6 @@
"version": "18.3.0",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
"integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
- "dev": true,
"dependencies": {
"@types/react": "*"
}
@@ -3411,6 +3473,15 @@
"@types/react": "*"
}
},
+ "node_modules/@types/react-reconciler": {
+ "version": "0.28.9",
+ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz",
+ "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/react-transition-group": {
"version": "4.4.10",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz",
@@ -3425,6 +3496,13 @@
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
"dev": true
},
+ "node_modules/@types/stylis": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz",
+ "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==",
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz",
@@ -4406,6 +4484,16 @@
"node": ">= 6"
}
},
+ "node_modules/camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+ "license": "MIT",
+ "peer": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/caniuse-lite": {
"version": "1.0.30001636",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz",
@@ -4850,6 +4938,28 @@
"node": ">= 8"
}
},
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+ "license": "ISC",
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
"node_modules/css.escape": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
@@ -7208,6 +7318,18 @@
"set-function-name": "^2.0.1"
}
},
+ "node_modules/its-fine": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.2.5.tgz",
+ "integrity": "sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/react-reconciler": "^0.28.0"
+ },
+ "peerDependencies": {
+ "react": ">=18.0"
+ }
+ },
"node_modules/jackspeak": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz",
@@ -7440,6 +7562,27 @@
"json-buffer": "3.0.1"
}
},
+ "node_modules/konva": {
+ "version": "9.3.22",
+ "resolved": "https://registry.npmjs.org/konva/-/konva-9.3.22.tgz",
+ "integrity": "sha512-yQI5d1bmELlD/fowuyfOp9ff+oamg26WOCkyqUyc+nczD/lhRa3EvD2MZOoc4c1293TAubW9n34fSQLgSeEgSw==",
+ "funding": [
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/lavrton"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/konva"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/lavrton"
+ }
+ ],
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/lazy-ass": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz",
@@ -7753,8 +7896,7 @@
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
},
"node_modules/lodash.mergewith": {
"version": "4.6.2",
@@ -8170,7 +8312,6 @@
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -8746,9 +8887,10 @@
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info."
},
"node_modules/picocolors": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
- "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew=="
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.1",
@@ -8888,10 +9030,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.38",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
- "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
- "dev": true,
+ "version": "8.4.49",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
+ "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
"funding": [
{
"type": "opencollective",
@@ -8906,10 +9047,11 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
"nanoid": "^3.3.7",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.2.0"
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -9050,8 +9192,7 @@
"node_modules/postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
- "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
- "dev": true
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
"node_modules/prelude-ls": {
"version": "1.2.1",
@@ -9455,6 +9596,62 @@
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
"integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw=="
},
+ "node_modules/react-filerobot-image-editor": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/react-filerobot-image-editor/-/react-filerobot-image-editor-4.9.1.tgz",
+ "integrity": "sha512-O9xFySHT6MKuNXAKJMVGG2wyMeaV9NxHIVyBWzhysdbaxx7fZO0r4aQsBFkYt7+0B3Se5/33Sv90r8t3274Q+w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.17.2",
+ "@scaleflex/icons": "2.10.27",
+ "@scaleflex/ui": "2.10.27",
+ "konva": "9.3.6",
+ "prop-types": "15.7.2"
+ },
+ "peerDependencies": {
+ "react": ">=17.0.0",
+ "react-dom": ">=17.0.0",
+ "react-konva": ">=17.0.0",
+ "styled-components": ">=5.3.5"
+ }
+ },
+ "node_modules/react-filerobot-image-editor/node_modules/konva": {
+ "version": "9.3.6",
+ "resolved": "https://registry.npmjs.org/konva/-/konva-9.3.6.tgz",
+ "integrity": "sha512-dqR8EbcM0hjuilZCBP6xauQ5V3kH3m9kBcsDkqPypQuRgsXbcXUrxqYxhNbdvKZpYNW8Amq94jAD/C0NY3qfBQ==",
+ "funding": [
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/lavrton"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/konva"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/lavrton"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/react-filerobot-image-editor/node_modules/prop-types": {
+ "version": "15.7.2",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
+ "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.8.1"
+ }
+ },
+ "node_modules/react-filerobot-image-editor/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT"
+ },
"node_modules/react-helmet": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz",
@@ -9489,6 +9686,53 @@
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true
},
+ "node_modules/react-konva": {
+ "version": "18.2.10",
+ "resolved": "https://registry.npmjs.org/react-konva/-/react-konva-18.2.10.tgz",
+ "integrity": "sha512-ohcX1BJINL43m4ynjZ24MxFI1syjBdrXhqVxYVDw2rKgr3yuS0x/6m1Y2Z4sl4T/gKhfreBx8KHisd0XC6OT1g==",
+ "funding": [
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/lavrton"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/konva"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/lavrton"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@types/react-reconciler": "^0.28.2",
+ "its-fine": "^1.1.1",
+ "react-reconciler": "~0.29.0",
+ "scheduler": "^0.23.0"
+ },
+ "peerDependencies": {
+ "konva": "^8.0.1 || ^7.2.5 || ^9.0.0",
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/react-reconciler": {
+ "version": "0.29.2",
+ "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.2.tgz",
+ "integrity": "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
"node_modules/react-router": {
"version": "6.23.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz",
@@ -9955,6 +10199,13 @@
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"license": "MIT"
},
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -10094,10 +10345,10 @@
}
},
"node_modules/source-map-js": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
- "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
- "dev": true,
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
@@ -10549,6 +10800,49 @@
"url": "https://github.com/sponsors/Borewit"
}
},
+ "node_modules/styled-components": {
+ "version": "6.1.19",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.19.tgz",
+ "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@emotion/is-prop-valid": "1.2.2",
+ "@emotion/unitless": "0.8.1",
+ "@types/stylis": "4.2.5",
+ "css-to-react-native": "3.2.0",
+ "csstype": "3.1.3",
+ "postcss": "8.4.49",
+ "shallowequal": "1.1.0",
+ "stylis": "4.3.2",
+ "tslib": "2.6.2"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/styled-components"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0",
+ "react-dom": ">= 16.8.0"
+ }
+ },
+ "node_modules/styled-components/node_modules/stylis": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz",
+ "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/styled-components/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
+ "license": "0BSD",
+ "peer": true
+ },
"node_modules/stylis": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
@@ -10826,6 +11120,15 @@
"node": ">=14.0.0"
}
},
+ "node_modules/tippy.js": {
+ "version": "6.3.7",
+ "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
+ "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@popperjs/core": "^2.9.0"
+ }
+ },
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
diff --git a/package.json b/package.json
index 6405554..6e2992c 100644
--- a/package.json
+++ b/package.json
@@ -65,8 +65,10 @@
"rc-slider": "^11.1.8",
"react": "^18.3.1",
"react-dom": "^18.3.1",
+ "react-filerobot-image-editor": "^4.9.1",
"react-helmet": "^6.1.0",
"react-image-crop": "^11.0.7",
+ "react-konva": "^18.2.10",
"react-router-dom": "^6.23.1",
"tesseract.js": "^6.0.0",
"type-fest": "^4.35.0",
diff --git a/src/components/ToolInputAndResult.tsx b/src/components/ToolInputAndResult.tsx
index 390f560..ccf7837 100644
--- a/src/components/ToolInputAndResult.tsx
+++ b/src/components/ToolInputAndResult.tsx
@@ -12,7 +12,7 @@ export default function ToolInputAndResult({
return (
{input && (
-
+
{input}
)}
diff --git a/src/pages/tools/image/generic/editor/index.tsx b/src/pages/tools/image/generic/editor/index.tsx
new file mode 100644
index 0000000..97f5176
--- /dev/null
+++ b/src/pages/tools/image/generic/editor/index.tsx
@@ -0,0 +1,127 @@
+import React, { useCallback, useState } from 'react';
+import { Box } from '@mui/material';
+import ToolImageInput from '@components/input/ToolImageInput';
+import ToolContent from '@components/ToolContent';
+import { ToolComponentProps } from '@tools/defineTool';
+
+// Import the image editor with proper typing
+import FilerobotImageEditor, {
+ FilerobotImageEditorConfig
+} from 'react-filerobot-image-editor';
+
+export default function ImageEditor({ title }: ToolComponentProps) {
+ const [input, setInput] = useState(null);
+ const [isEditorOpen, setIsEditorOpen] = useState(false);
+ const [imageUrl, setImageUrl] = useState(null);
+
+ // Handle file input change
+ const handleInputChange = useCallback((file: File | null) => {
+ setInput(file);
+ if (file) {
+ // Create object URL for the image editor
+ const url = URL.createObjectURL(file);
+ setImageUrl(url);
+ setIsEditorOpen(true);
+ } else {
+ setImageUrl(null);
+ }
+ }, []);
+
+ const onCloseEditor = (reason: string) => {
+ if (reason === 'close-button-clicked') {
+ setIsEditorOpen(false);
+ setImageUrl(null);
+ }
+ };
+
+ // Handle save from image editor
+ const handleSave: FilerobotImageEditorConfig['onSave'] = (
+ editedImageObject,
+ designState
+ ) => {
+ console.log('Image saved:', editedImageObject, designState);
+
+ if (editedImageObject && editedImageObject.imageBase64) {
+ // Convert base64 to blob
+ const base64Data = editedImageObject.imageBase64.split(',')[1];
+ const byteCharacters = atob(base64Data);
+ const byteNumbers = new Array(byteCharacters.length);
+
+ for (let i = 0; i < byteCharacters.length; i++) {
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
+ }
+
+ const byteArray = new Uint8Array(byteNumbers);
+ const blob = new Blob([byteArray], { type: editedImageObject.mimeType });
+
+ const editedFile = new File(
+ [blob],
+ editedImageObject.fullName ?? 'edited.png',
+ {
+ type: editedImageObject.mimeType
+ }
+ );
+ // Create a temporary download link
+ const url = URL.createObjectURL(editedFile);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = editedFile.name; // This will be the name of the downloaded file
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+
+ // Release the blob URL
+ URL.revokeObjectURL(url);
+ }
+ };
+
+ const getDefaultImageName = () => {
+ if (!input) return;
+ const originalName = input?.name || 'edited-image';
+ const nameWithoutExt = originalName.replace(/\.[^/.]+$/, '');
+ const editedFileName = `${nameWithoutExt}-edited`;
+ return editedFileName;
+ };
+
+ return (
+
+
+
+ )
+ ) : (
+
+ )
+ }
+ toolInfo={{
+ title: 'Image Editor',
+ description:
+ 'A powerful image editing tool that provides professional-grade features including cropping, rotating, color adjustments, text annotations, drawing tools, and watermarking. Edit your images directly in your browser without the need for external software.'
+ }}
+ compute={() => {}}
+ />
+ );
+}
diff --git a/src/pages/tools/image/generic/editor/meta.ts b/src/pages/tools/image/generic/editor/meta.ts
new file mode 100644
index 0000000..2771325
--- /dev/null
+++ b/src/pages/tools/image/generic/editor/meta.ts
@@ -0,0 +1,28 @@
+import { defineTool } from '@tools/defineTool';
+import { lazy } from 'react';
+
+export const tool = defineTool('image-generic', {
+ name: 'Image Editor',
+ path: 'editor',
+ icon: 'mdi:image-edit',
+ description:
+ 'Advanced image editor with tools for cropping, rotating, annotating, adjusting colors, and adding watermarks. Edit your images with professional-grade tools directly in your browser.',
+ shortDescription: 'Edit images with advanced tools and features',
+ keywords: [
+ 'image',
+ 'editor',
+ 'edit',
+ 'crop',
+ 'rotate',
+ 'annotate',
+ 'adjust',
+ 'watermark',
+ 'text',
+ 'drawing',
+ 'filters',
+ 'brightness',
+ 'contrast',
+ 'saturation'
+ ],
+ component: lazy(() => import('./index'))
+});
diff --git a/src/pages/tools/image/generic/index.ts b/src/pages/tools/image/generic/index.ts
index c3710b9..d87de31 100644
--- a/src/pages/tools/image/generic/index.ts
+++ b/src/pages/tools/image/generic/index.ts
@@ -9,7 +9,9 @@ import { tool as imageToText } from './image-to-text/meta';
import { tool as qrCodeGenerator } from './qr-code/meta';
import { tool as rotateImage } from './rotate/meta';
import { tool as convertToJpg } from './convert-to-jpg/meta';
+import { tool as imageEditor } from './editor/meta';
export const imageGenericTools = [
+ imageEditor,
resizeImage,
compressImage,
removeBackground,