feat: edit image

This commit is contained in:
Ibrahima G. Coulibaly
2025-07-09 04:34:43 +01:00
parent 1250c8f4c3
commit 90e0389156
7 changed files with 522 additions and 63 deletions

83
.idea/workspace.xml generated
View File

@@ -4,14 +4,14 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="chore: png icon"> <list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="feat: convert to jpg">
<change beforePath="$PROJECT_DIR$/src/assets/logo.svg" beforeDir="false" /> <change afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/editor/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/xml/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/xml/index.ts" afterDir="false" /> <change afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/editor/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/xml/xml-viewer/index.tsx" beforeDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/xml/xml-viewer/meta.ts" beforeDir="false" /> <change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/xml/xml-viewer/service.ts" beforeDir="false" /> <change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/xml/xml-viewer/types.ts" beforeDir="false" /> <change beforePath="$PROJECT_DIR$/src/components/ToolInputAndResult.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/ToolInputAndResult.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/xml/xml-viewer/xml-viewer.service.test.ts" beforeDir="false" /> <change beforePath="$PROJECT_DIR$/src/pages/tools/image/generic/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/index.ts" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -309,19 +309,6 @@
</key> </key>
</component> </component>
<component name="RunManager" selected="npm.dev"> <component name="RunManager" selected="npm.dev">
<configuration name="calculateTimeBetweenDates" type="JavaScriptTestRunnerVitest" temporary="true" nameIsGenerated="true">
<node-interpreter value="project" />
<vitest-package value="$PROJECT_DIR$/node_modules/vitest" />
<working-dir value="$PROJECT_DIR$" />
<vitest-options value="--run" />
<envs />
<scope-kind value="SUITE" />
<test-file value="$PROJECT_DIR$/src/pages/tools/time/time-between-dates/time-between-dates.service.test.ts" />
<test-names>
<test-name value="calculateTimeBetweenDates" />
</test-names>
<method v="2" />
</configuration>
<configuration name="parsePageRanges" type="JavaScriptTestRunnerVitest" temporary="true" nameIsGenerated="true"> <configuration name="parsePageRanges" type="JavaScriptTestRunnerVitest" temporary="true" nameIsGenerated="true">
<node-interpreter value="project" /> <node-interpreter value="project" />
<vitest-package value="$PROJECT_DIR$/node_modules/vitest" /> <vitest-package value="$PROJECT_DIR$/node_modules/vitest" />
@@ -378,9 +365,19 @@
<envs /> <envs />
<method v="2" /> <method v="2" />
</configuration> </configuration>
<configuration name="test:e2e" type="js.build_tools.npm" temporary="true" nameIsGenerated="true">
<package-json value="$PROJECT_DIR$/package.json" />
<command value="run" />
<scripts>
<script value="test:e2e" />
</scripts>
<node-interpreter value="project" />
<envs />
<method v="2" />
</configuration>
<list> <list>
<item itemvalue="npm.test:e2e" />
<item itemvalue="npm.dev" /> <item itemvalue="npm.dev" />
<item itemvalue="Vitest.calculateTimeBetweenDates" />
<item itemvalue="Vitest.timeBetweenDates" /> <item itemvalue="Vitest.timeBetweenDates" />
<item itemvalue="Vitest.parsePageRanges" /> <item itemvalue="Vitest.parsePageRanges" />
<item itemvalue="Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp" /> <item itemvalue="Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp" />
@@ -388,10 +385,10 @@
<recent_temporary> <recent_temporary>
<list> <list>
<item itemvalue="npm.dev" /> <item itemvalue="npm.dev" />
<item itemvalue="npm.test:e2e" />
<item itemvalue="Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp" /> <item itemvalue="Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp" />
<item itemvalue="Vitest.parsePageRanges" /> <item itemvalue="Vitest.parsePageRanges" />
<item itemvalue="Vitest.timeBetweenDates" /> <item itemvalue="Vitest.timeBetweenDates" />
<item itemvalue="Vitest.calculateTimeBetweenDates" />
</list> </list>
</recent_temporary> </recent_temporary>
</component> </component>
@@ -498,22 +495,6 @@
<workItem from="1749047510481" duration="879000" /> <workItem from="1749047510481" duration="879000" />
<workItem from="1751846528195" duration="4358000" /> <workItem from="1751846528195" duration="4358000" />
</task> </task>
<task id="LOCAL-00160" summary="fix: tools by category scroll">
<option name="closed" value="true" />
<created>1741548044897</created>
<option name="number" value="00160" />
<option name="presentableId" value="LOCAL-00160" />
<option name="project" value="LOCAL" />
<updated>1741548044897</updated>
</task>
<task id="LOCAL-00161" summary="fix: missing meta">
<option name="closed" value="true" />
<created>1741568170877</created>
<option name="number" value="00161" />
<option name="presentableId" value="LOCAL-00161" />
<option name="project" value="LOCAL" />
<updated>1741568170877</updated>
</task>
<task id="LOCAL-00162" summary="feat: trim video"> <task id="LOCAL-00162" summary="feat: trim video">
<option name="closed" value="true" /> <option name="closed" value="true" />
<created>1741580004784</created> <created>1741580004784</created>
@@ -890,7 +871,23 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1752023182341</updated> <updated>1752023182341</updated>
</task> </task>
<option name="localTasksCounter" value="209" /> <task id="LOCAL-00209" summary="fix: remove xml viewer">
<option name="closed" value="true" />
<created>1752023796004</created>
<option name="number" value="00209" />
<option name="presentableId" value="LOCAL-00209" />
<option name="project" value="LOCAL" />
<updated>1752023796004</updated>
</task>
<task id="LOCAL-00210" summary="feat: convert to jpg">
<option name="closed" value="true" />
<created>1752026153328</created>
<option name="number" value="00210" />
<option name="presentableId" value="LOCAL-00210" />
<option name="project" value="LOCAL" />
<updated>1752026153328</updated>
</task>
<option name="localTasksCounter" value="211" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@@ -937,8 +934,6 @@
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" /> <option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
<option name="CHECK_NEW_TODO" value="false" /> <option name="CHECK_NEW_TODO" value="false" />
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" /> <option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
<MESSAGE value="refactor: lib" />
<MESSAGE value="fix: path" />
<MESSAGE value="fix: vite worker format" /> <MESSAGE value="fix: vite worker format" />
<MESSAGE value="fix: tests" /> <MESSAGE value="fix: tests" />
<MESSAGE value="chore: uninstall @jspawn/ghostscript-wasm" /> <MESSAGE value="chore: uninstall @jspawn/ghostscript-wasm" />
@@ -962,7 +957,9 @@
<MESSAGE value="chore: new logo and font" /> <MESSAGE value="chore: new logo and font" />
<MESSAGE value="chore: white logo" /> <MESSAGE value="chore: white logo" />
<MESSAGE value="chore: png icon" /> <MESSAGE value="chore: png icon" />
<option name="LAST_COMMIT_MESSAGE" value="chore: png icon" /> <MESSAGE value="fix: remove xml viewer" />
<MESSAGE value="feat: convert to jpg" />
<option name="LAST_COMMIT_MESSAGE" value="feat: convert to jpg" />
</component> </component>
<component name="VgoProject"> <component name="VgoProject">
<integration-enabled>false</integration-enabled> <integration-enabled>false</integration-enabled>

341
package-lock.json generated
View File

@@ -48,8 +48,10 @@
"rc-slider": "^11.1.8", "rc-slider": "^11.1.8",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-filerobot-image-editor": "^4.9.1",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-image-crop": "^11.0.7", "react-image-crop": "^11.0.7",
"react-konva": "^18.2.10",
"react-router-dom": "^6.23.1", "react-router-dom": "^6.23.1",
"tesseract.js": "^6.0.0", "tesseract.js": "^6.0.0",
"type-fest": "^4.35.0", "type-fest": "^4.35.0",
@@ -2906,6 +2908,45 @@
"win32" "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": { "node_modules/@sideway/address": {
"version": "4.1.5", "version": "4.1.5",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
@@ -3259,6 +3300,19 @@
"react-dom": "^18.0.0" "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": { "node_modules/@tokenizer/token": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", "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", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.5.tgz",
"integrity": "sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw==" "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": { "node_modules/@types/morsee": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/@types/morsee/-/morsee-1.0.2.tgz", "resolved": "https://registry.npmjs.org/@types/morsee/-/morsee-1.0.2.tgz",
@@ -3397,7 +3460,6 @@
"version": "18.3.0", "version": "18.3.0",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
"integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
"dev": true,
"dependencies": { "dependencies": {
"@types/react": "*" "@types/react": "*"
} }
@@ -3411,6 +3473,15 @@
"@types/react": "*" "@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": { "node_modules/@types/react-transition-group": {
"version": "4.4.10", "version": "4.4.10",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz",
@@ -3425,6 +3496,13 @@
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
"dev": true "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": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "6.21.0", "version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz",
@@ -4406,6 +4484,16 @@
"node": ">= 6" "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": { "node_modules/caniuse-lite": {
"version": "1.0.30001636", "version": "1.0.30001636",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz",
@@ -4850,6 +4938,28 @@
"node": ">= 8" "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": { "node_modules/css.escape": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
@@ -7208,6 +7318,18 @@
"set-function-name": "^2.0.1" "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": { "node_modules/jackspeak": {
"version": "3.4.0", "version": "3.4.0",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz",
@@ -7440,6 +7562,27 @@
"json-buffer": "3.0.1" "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": { "node_modules/lazy-ass": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz",
@@ -7753,8 +7896,7 @@
"node_modules/lodash.merge": { "node_modules/lodash.merge": {
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
"dev": true
}, },
"node_modules/lodash.mergewith": { "node_modules/lodash.mergewith": {
"version": "4.6.2", "version": "4.6.2",
@@ -8170,7 +8312,6 @@
"version": "3.3.7", "version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -8746,9 +8887,10 @@
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info." "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info."
}, },
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.0.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.1", "version": "2.3.1",
@@ -8888,10 +9030,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.38", "version": "8.4.49",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -8906,10 +9047,11 @@
"url": "https://github.com/sponsors/ai" "url": "https://github.com/sponsors/ai"
} }
], ],
"license": "MIT",
"dependencies": { "dependencies": {
"nanoid": "^3.3.7", "nanoid": "^3.3.7",
"picocolors": "^1.0.0", "picocolors": "^1.1.1",
"source-map-js": "^1.2.0" "source-map-js": "^1.2.1"
}, },
"engines": { "engines": {
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
@@ -9050,8 +9192,7 @@
"node_modules/postcss-value-parser": { "node_modules/postcss-value-parser": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
"dev": true
}, },
"node_modules/prelude-ls": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
@@ -9455,6 +9596,62 @@
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
"integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" "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": { "node_modules/react-helmet": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz",
@@ -9489,6 +9686,53 @@
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true "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": { "node_modules/react-router": {
"version": "6.23.1", "version": "6.23.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz", "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==", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"license": "MIT" "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": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -10094,10 +10345,10 @@
} }
}, },
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.0", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true, "license": "BSD-3-Clause",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -10549,6 +10800,49 @@
"url": "https://github.com/sponsors/Borewit" "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": { "node_modules/stylis": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
@@ -10826,6 +11120,15 @@
"node": ">=14.0.0" "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": { "node_modules/to-fast-properties": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",

View File

@@ -65,8 +65,10 @@
"rc-slider": "^11.1.8", "rc-slider": "^11.1.8",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-filerobot-image-editor": "^4.9.1",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-image-crop": "^11.0.7", "react-image-crop": "^11.0.7",
"react-konva": "^18.2.10",
"react-router-dom": "^6.23.1", "react-router-dom": "^6.23.1",
"tesseract.js": "^6.0.0", "tesseract.js": "^6.0.0",
"type-fest": "^4.35.0", "type-fest": "^4.35.0",

View File

@@ -12,7 +12,7 @@ export default function ToolInputAndResult({
return ( return (
<Grid id="tool" container spacing={2}> <Grid id="tool" container spacing={2}>
{input && ( {input && (
<Grid item xs={12} md={6}> <Grid item xs={12} md={result ? 6 : 12}>
{input} {input}
</Grid> </Grid>
)} )}

View File

@@ -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<File | null>(null);
const [isEditorOpen, setIsEditorOpen] = useState(false);
const [imageUrl, setImageUrl] = useState<string | null>(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 (
<ToolContent
title={title}
initialValues={{}}
getGroups={null}
input={input}
inputComponent={
isEditorOpen ? (
imageUrl && (
<Box style={{ width: '100%', height: '70vh' }}>
<FilerobotImageEditor
source={imageUrl}
onSave={handleSave}
onClose={onCloseEditor}
annotationsCommon={{
fill: 'blue'
}}
defaultSavedImageName={getDefaultImageName()}
Rotate={{ angle: 90, componentType: 'slider' }}
savingPixelRatio={1}
previewPixelRatio={1}
/>
</Box>
)
) : (
<ToolImageInput
value={input}
onChange={handleInputChange}
accept={['image/*']}
title="Upload Image to Edit"
/>
)
}
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={() => {}}
/>
);
}

View File

@@ -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'))
});

View File

@@ -9,7 +9,9 @@ import { tool as imageToText } from './image-to-text/meta';
import { tool as qrCodeGenerator } from './qr-code/meta'; import { tool as qrCodeGenerator } from './qr-code/meta';
import { tool as rotateImage } from './rotate/meta'; import { tool as rotateImage } from './rotate/meta';
import { tool as convertToJpg } from './convert-to-jpg/meta'; import { tool as convertToJpg } from './convert-to-jpg/meta';
import { tool as imageEditor } from './editor/meta';
export const imageGenericTools = [ export const imageGenericTools = [
imageEditor,
resizeImage, resizeImage,
compressImage, compressImage,
removeBackground, removeBackground,