Merge branch 'main' into text-statistics

This commit is contained in:
Chesterkxng
2025-06-05 22:51:47 +02:00
8 changed files with 847 additions and 71 deletions

128
.idea/workspace.xml generated
View File

@@ -4,8 +4,12 @@
<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="fix: compute flow"> <list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="feat: qr code generation init">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/options/ColorSelector.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/options/ColorSelector.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/generic/qr-code/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/qr-code/index.tsx" 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" />
@@ -22,7 +26,7 @@
<option name="PUSH_AUTO_UPDATE" value="true" /> <option name="PUSH_AUTO_UPDATE" value="true" />
<option name="RECENT_BRANCH_BY_REPOSITORY"> <option name="RECENT_BRANCH_BY_REPOSITORY">
<map> <map>
<entry key="$PROJECT_DIR$" value="chesterkxng" /> <entry key="$PROJECT_DIR$" value="main" />
</map> </map>
</option> </option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
@@ -199,56 +203,56 @@
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent">{ <component name="PropertiesComponent"><![CDATA[{
&quot;keyToString&quot;: { "keyToString": {
&quot;ASKED_ADD_EXTERNAL_FILES&quot;: &quot;true&quot;, "ASKED_ADD_EXTERNAL_FILES": "true",
&quot;ASKED_SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;, "ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
&quot;Docker.Dockerfile build.executor&quot;: &quot;Run&quot;, "Docker.Dockerfile build.executor": "Run",
&quot;Docker.Dockerfile.executor&quot;: &quot;Run&quot;, "Docker.Dockerfile.executor": "Run",
&quot;Playwright.Create transparent PNG.should make png color transparent.executor&quot;: &quot;Run&quot;, "Playwright.Create transparent PNG.should make png color transparent.executor": "Run",
&quot;Playwright.JoinText Component.executor&quot;: &quot;Run&quot;, "Playwright.JoinText Component.executor": "Run",
&quot;Playwright.JoinText Component.should merge text pieces with specified join character.executor&quot;: &quot;Run&quot;, "Playwright.JoinText Component.should merge text pieces with specified join character.executor": "Run",
&quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;, "RunOnceActivity.OpenProjectViewOnStart": "true",
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;, "RunOnceActivity.ShowReadmeOnStart": "true",
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;, "RunOnceActivity.git.unshallow": "true",
&quot;Vitest.compute function (1).executor&quot;: &quot;Run&quot;, "Vitest.compute function (1).executor": "Run",
&quot;Vitest.compute function.executor&quot;: &quot;Run&quot;, "Vitest.compute function.executor": "Run",
&quot;Vitest.mergeText.executor&quot;: &quot;Run&quot;, "Vitest.mergeText.executor": "Run",
&quot;Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor&quot;: &quot;Run&quot;, "Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor": "Run",
&quot;Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor&quot;: &quot;Run&quot;, "Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor": "Run",
&quot;Vitest.parsePageRanges.executor&quot;: &quot;Run&quot;, "Vitest.parsePageRanges.executor": "Run",
&quot;Vitest.removeDuplicateLines function.executor&quot;: &quot;Run&quot;, "Vitest.removeDuplicateLines function.executor": "Run",
&quot;Vitest.removeDuplicateLines function.newlines option.executor&quot;: &quot;Run&quot;, "Vitest.removeDuplicateLines function.newlines option.executor": "Run",
&quot;Vitest.removeDuplicateLines function.newlines option.should filter newlines when newlines is set to filter.executor&quot;: &quot;Run&quot;, "Vitest.removeDuplicateLines function.newlines option.should filter newlines when newlines is set to filter.executor": "Run",
&quot;Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor&quot;: &quot;Run&quot;, "Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run",
&quot;Vitest.replaceText function.executor&quot;: &quot;Run&quot;, "Vitest.replaceText function.executor": "Run",
&quot;Vitest.timeBetweenDates.executor&quot;: &quot;Run&quot;, "Vitest.timeBetweenDates.executor": "Run",
&quot;git-widget-placeholder&quot;: &quot;#131 on fork/ARRY7686/main&quot;, "git-widget-placeholder": "qr-code",
&quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;, "ignore.virus.scanning.warn.message": "true",
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;, "kotlin-language-version-configured": "true",
&quot;last_opened_file_path&quot;: &quot;C:/Users/Ibrahima/IdeaProjects/omni-tools/src&quot;, "last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/src",
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, "node.js.detected.package.eslint": "true",
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, "node.js.detected.package.tslint": "true",
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;, "node.js.selected.package.eslint": "(autodetect)",
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;, "node.js.selected.package.tslint": "(autodetect)",
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;, "nodejs_package_manager_path": "npm",
&quot;npm.build.executor&quot;: &quot;Run&quot;, "npm.build.executor": "Run",
&quot;npm.dev.executor&quot;: &quot;Run&quot;, "npm.dev.executor": "Run",
&quot;npm.lint.executor&quot;: &quot;Run&quot;, "npm.lint.executor": "Run",
&quot;npm.prebuild.executor&quot;: &quot;Run&quot;, "npm.prebuild.executor": "Run",
&quot;npm.script:create:tool.executor&quot;: &quot;Run&quot;, "npm.script:create:tool.executor": "Run",
&quot;npm.test.executor&quot;: &quot;Run&quot;, "npm.test.executor": "Run",
&quot;npm.test:e2e.executor&quot;: &quot;Run&quot;, "npm.test:e2e.executor": "Run",
&quot;npm.test:e2e:run.executor&quot;: &quot;Run&quot;, "npm.test:e2e:run.executor": "Run",
&quot;prettierjs.PrettierConfiguration.Package&quot;: &quot;C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier&quot;, "prettierjs.PrettierConfiguration.Package": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier",
&quot;project.structure.last.edited&quot;: &quot;Problems&quot;, "project.structure.last.edited": "Problems",
&quot;project.structure.proportion&quot;: &quot;0.0&quot;, "project.structure.proportion": "0.0",
&quot;project.structure.side.proportion&quot;: &quot;0.2&quot;, "project.structure.side.proportion": "0.2",
&quot;settings.editor.selected.configurable&quot;: &quot;refactai_advanced_settings&quot;, "settings.editor.selected.configurable": "refactai_advanced_settings",
&quot;ts.external.directory.path&quot;: &quot;C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib&quot;, "ts.external.directory.path": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib",
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot; "vue.rearranger.settings.migration": "true"
} }
}</component> }]]></component>
<component name="ReactDesignerToolWindowState"> <component name="ReactDesignerToolWindowState">
<option name="myId2Visible"> <option name="myId2Visible">
<map> <map>
@@ -463,14 +467,6 @@
<workItem from="1748282636141" duration="478000" /> <workItem from="1748282636141" duration="478000" />
<workItem from="1749047510481" duration="879000" /> <workItem from="1749047510481" duration="879000" />
</task> </task>
<task id="LOCAL-00151" summary="feat: crop png">
<option name="closed" value="true" />
<created>1741487735223</created>
<option name="number" value="00151" />
<option name="presentableId" value="LOCAL-00151" />
<option name="project" value="LOCAL" />
<updated>1741487735223</updated>
</task>
<task id="LOCAL-00152" summary="feat: crop png"> <task id="LOCAL-00152" summary="feat: crop png">
<option name="closed" value="true" /> <option name="closed" value="true" />
<created>1741492688761</created> <created>1741492688761</created>
@@ -855,7 +851,15 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1748881153433</updated> <updated>1748881153433</updated>
</task> </task>
<option name="localTasksCounter" value="200" /> <task id="LOCAL-00200" summary="feat: qr code generation init">
<option name="closed" value="true" />
<created>1749147227565</created>
<option name="number" value="00200" />
<option name="presentableId" value="LOCAL-00200" />
<option name="project" value="LOCAL" />
<updated>1749147227565</updated>
</task>
<option name="localTasksCounter" value="201" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@@ -902,7 +906,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="chore: compress video icon" />
<MESSAGE value="fix: gif speed" /> <MESSAGE value="fix: gif speed" />
<MESSAGE value="fix: tsc" /> <MESSAGE value="fix: tsc" />
<MESSAGE value="fix: background color" /> <MESSAGE value="fix: background color" />
@@ -927,7 +930,8 @@
<MESSAGE value="fix: misc" /> <MESSAGE value="fix: misc" />
<MESSAGE value="chore: remove unnecessary prop" /> <MESSAGE value="chore: remove unnecessary prop" />
<MESSAGE value="fix: compute flow" /> <MESSAGE value="fix: compute flow" />
<option name="LAST_COMMIT_MESSAGE" value="fix: compute flow" /> <MESSAGE value="feat: qr code generation init" />
<option name="LAST_COMMIT_MESSAGE" value="feat: qr code generation init" />
</component> </component>
<component name="XSLT-Support.FileAssociations.UIState"> <component name="XSLT-Support.FileAssociations.UIState">
<expand /> <expand />

234
package-lock.json generated
View File

@@ -41,6 +41,7 @@
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"pdfjs-dist": "^5.2.133", "pdfjs-dist": "^5.2.133",
"playwright": "^1.45.0", "playwright": "^1.45.0",
"qrcode": "^1.5.4",
"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",
@@ -61,6 +62,7 @@
"@types/color": "^3.0.6", "@types/color": "^3.0.6",
"@types/color-rgba": "^2.1.2", "@types/color-rgba": "^2.1.2",
"@types/node": "^20.12.12", "@types/node": "^20.12.12",
"@types/qrcode": "^1.5.5",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@types/react-helmet": "^6.1.11", "@types/react-helmet": "^6.1.11",
@@ -3369,6 +3371,16 @@
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
}, },
"node_modules/@types/qrcode": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.5.tgz",
"integrity": "sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "18.3.3", "version": "18.3.3",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
@@ -3867,7 +3879,6 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@@ -3876,7 +3887,6 @@
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": { "dependencies": {
"color-convert": "^2.0.1" "color-convert": "^2.0.1"
}, },
@@ -4375,6 +4385,15 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/camelcase-css": { "node_modules/camelcase-css": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
@@ -4921,6 +4940,15 @@
} }
} }
}, },
"node_modules/decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/deep-eql": { "node_modules/deep-eql": {
"version": "4.1.4", "version": "4.1.4",
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz",
@@ -5045,6 +5073,12 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0" "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
} }
}, },
"node_modules/dijkstrajs": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
"license": "MIT"
},
"node_modules/dir-glob": { "node_modules/dir-glob": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -6105,7 +6139,6 @@
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
"engines": { "engines": {
"node": "6.* || 8.* || >= 10.*" "node": "6.* || 8.* || >= 10.*"
} }
@@ -6848,7 +6881,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@@ -8478,6 +8510,15 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/package-json-from-dist": { "node_modules/package-json-from-dist": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
@@ -8546,7 +8587,6 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@@ -9129,6 +9169,170 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/qrcode": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
"license": "MIT",
"dependencies": {
"dijkstrajs": "^1.0.1",
"pngjs": "^5.0.0",
"yargs": "^15.3.1"
},
"bin": {
"qrcode": "bin/qrcode"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/qrcode/node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"node_modules/qrcode/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/qrcode/node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"license": "MIT",
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"license": "MIT",
"dependencies": {
"p-locate": "^4.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"license": "MIT",
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/qrcode/node_modules/p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"license": "MIT",
"dependencies": {
"p-limit": "^2.2.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/pngjs": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
"license": "MIT",
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/qrcode/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"license": "ISC"
},
"node_modules/qrcode/node_modules/yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"license": "MIT",
"dependencies": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"license": "ISC",
"dependencies": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/queue-microtask": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -9412,7 +9616,6 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -9426,6 +9629,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"license": "ISC"
},
"node_modules/resolve": { "node_modules/resolve": {
"version": "2.0.0-next.5", "version": "2.0.0-next.5",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
@@ -9666,6 +9875,12 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"license": "ISC"
},
"node_modules/set-function-length": { "node_modules/set-function-length": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -10197,7 +10412,6 @@
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-regex": "^5.0.1" "ansi-regex": "^5.0.1"
}, },
@@ -11203,6 +11417,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/which-module": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
"license": "ISC"
},
"node_modules/which-typed-array": { "node_modules/which-typed-array": {
"version": "1.1.15", "version": "1.1.15",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz",

View File

@@ -58,6 +58,7 @@
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"pdfjs-dist": "^5.2.133", "pdfjs-dist": "^5.2.133",
"playwright": "^1.45.0", "playwright": "^1.45.0",
"qrcode": "^1.5.4",
"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",
@@ -78,6 +79,7 @@
"@types/color": "^3.0.6", "@types/color": "^3.0.6",
"@types/color-rgba": "^2.1.2", "@types/color-rgba": "^2.1.2",
"@types/node": "^20.12.12", "@types/node": "^20.12.12",
"@types/qrcode": "^1.5.5",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@types/react-helmet": "^6.1.11", "@types/react-helmet": "^6.1.11",

View File

@@ -39,7 +39,7 @@ const ColorSelector: React.FC<ColorSelectorProps & TextFieldProps> = ({
<PaletteIcon /> <PaletteIcon />
</IconButton> </IconButton>
<TextField <TextField
style={{ display: 'none' }} style={{ visibility: 'hidden' }}
inputRef={inputRef} inputRef={inputRef}
type="color" type="color"
value={color} value={color}

View File

@@ -6,6 +6,7 @@ import { tool as cropImage } from './crop/meta';
import { tool as changeOpacity } from './change-opacity/meta'; import { tool as changeOpacity } from './change-opacity/meta';
import { tool as createTransparent } from './create-transparent/meta'; import { tool as createTransparent } from './create-transparent/meta';
import { tool as imageToText } from './image-to-text/meta'; import { tool as imageToText } from './image-to-text/meta';
import { tool as qrCodeGenerator } from './qr-code/meta';
export const imageGenericTools = [ export const imageGenericTools = [
resizeImage, resizeImage,
@@ -15,5 +16,6 @@ export const imageGenericTools = [
changeOpacity, changeOpacity,
changeColors, changeColors,
createTransparent, createTransparent,
imageToText imageToText,
qrCodeGenerator
]; ];

View File

@@ -0,0 +1,472 @@
import React, { useCallback, useState } from 'react';
import { Box, MenuItem, TextField } from '@mui/material';
import * as Yup from 'yup';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
import { GetGroupsType } from '@components/options/ToolOptions';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import { InitialValuesType, QRCodeType, WifiEncryptionType } from './types';
import ColorSelector from '@components/options/ColorSelector';
import ToolFileResult from '@components/result/ToolFileResult';
import * as QRCode from 'qrcode';
import { debounce } from 'lodash';
const initialValues: InitialValuesType = {
qrCodeType: 'URL',
// Common settings
size: '200',
bgColor: '#FFFFFF',
fgColor: '#000000',
// URL
url: 'https://example.com',
// Text
text: '',
// Email
emailAddress: '',
emailSubject: '',
emailBody: '',
// Phone
phoneNumber: '',
// SMS
smsNumber: '',
smsMessage: '',
// WiFi
wifiSsid: '',
wifiPassword: '',
wifiEncryption: 'WPA/WPA2',
// vCard
vCardName: '',
vCardEmail: '',
vCardPhone: '',
vCardAddress: '',
vCardCompany: '',
vCardTitle: '',
vCardWebsite: ''
};
// Function to format the QR code data based on the type
const formatQRCodeData = (values: InitialValuesType): string => {
switch (values.qrCodeType) {
case 'URL':
return values.url;
case 'Text':
return values.text;
case 'Email': {
let emailData = `mailto:${values.emailAddress}`;
if (values.emailSubject || values.emailBody) {
emailData += '?';
if (values.emailSubject) {
emailData += `subject=${encodeURIComponent(values.emailSubject)}`;
}
if (values.emailBody) {
emailData += `${
values.emailSubject ? '&' : ''
}body=${encodeURIComponent(values.emailBody)}`;
}
}
return emailData;
}
case 'Phone':
return `tel:${values.phoneNumber}`;
case 'SMS':
return `sms:${values.smsNumber}${
values.smsMessage
? `?body=${encodeURIComponent(values.smsMessage)}`
: ''
}`;
case 'WiFi': {
const encryption =
values.wifiEncryption === 'None' ? 'nopass' : values.wifiEncryption;
return `WIFI:T:${encryption};S:${values.wifiSsid};P:${values.wifiPassword};;`;
}
case 'vCard':
return `BEGIN:VCARD
VERSION:3.0
N:${values.vCardName}
FN:${values.vCardName}
ORG:${values.vCardCompany}
TITLE:${values.vCardTitle}
TEL:${values.vCardPhone}
EMAIL:${values.vCardEmail}
ADR:${values.vCardAddress}
URL:${values.vCardWebsite}
END:VCARD`;
default:
return '';
}
};
const validationSchema = Yup.object().shape({
qrCodeType: Yup.string().required('QR code type is required'),
size: Yup.number()
.min(100, 'Size must be at least 100px')
.max(1000, 'Size must be at most 1000px')
.required('Size is required'),
bgColor: Yup.string().required('Background color is required'),
fgColor: Yup.string().required('Foreground color is required'),
// URL
url: Yup.string().when('qrCodeType', {
is: 'URL',
then: (schema) =>
schema.url('Please enter a valid URL').required('URL is required')
}),
// Text
text: Yup.string().when('qrCodeType', {
is: 'Text',
then: (schema) => schema.required('Text is required')
}),
// Email
emailAddress: Yup.string().when('qrCodeType', {
is: 'Email',
then: (schema) =>
schema
.email('Please enter a valid email address')
.required('Email address is required')
}),
// Phone
phoneNumber: Yup.string().when('qrCodeType', {
is: 'Phone',
then: (schema) => schema.required('Phone number is required')
}),
// SMS
smsNumber: Yup.string().when('qrCodeType', {
is: 'SMS',
then: (schema) => schema.required('Phone number is required')
}),
// WiFi
wifiSsid: Yup.string().when('qrCodeType', {
is: 'WiFi',
then: (schema) => schema.required('SSID is required')
}),
// vCard
vCardName: Yup.string().when('qrCodeType', {
is: 'vCard',
then: (schema) => schema.required('Name is required')
})
});
export default function QRCodeGenerator({ title }: ToolComponentProps) {
const [result, setResult] = useState<File | null>(null);
const getGroups: GetGroupsType<InitialValuesType> = ({
values,
updateField
}) => {
return [
{
title: 'QR Code Type',
component: (
<Box>
<TextField
select
fullWidth
value={values.qrCodeType}
onChange={(e) =>
updateField('qrCodeType', e.target.value as QRCodeType)
}
label="Select QR Code Type"
margin="normal"
>
<MenuItem value="URL">URL</MenuItem>
<MenuItem value="Text">Text</MenuItem>
<MenuItem value="Email">Email</MenuItem>
<MenuItem value="Phone">Phone</MenuItem>
<MenuItem value="SMS">SMS</MenuItem>
<MenuItem value="WiFi">WiFi</MenuItem>
<MenuItem value="vCard">vCard (Contact)</MenuItem>
</TextField>
</Box>
)
},
{
title: 'QR Code Settings',
component: (
<Box>
<TextFieldWithDesc
value={values.size}
onOwnChange={(val) => updateField('size', val)}
description="Size in pixels (100-1000)"
inputProps={{
type: 'number',
min: 100,
max: 1000
}}
/>
<ColorSelector
description="Background Color"
value={values.bgColor}
onColorChange={(val) => updateField('bgColor', val)}
/>
<ColorSelector
description="Foreground Color"
value={values.fgColor}
onColorChange={(val) => updateField('fgColor', val)}
/>
</Box>
)
},
// Dynamic form fields based on QR code type
{
title: `${values.qrCodeType} Details`,
component: (
<Box>
{values.qrCodeType === 'URL' && (
<TextFieldWithDesc
value={values.url}
onOwnChange={(val) => updateField('url', val)}
description="Enter the URL"
inputProps={{
placeholder: 'https://example.com'
}}
/>
)}
{values.qrCodeType === 'Text' && (
<TextFieldWithDesc
value={values.text}
onOwnChange={(val) => updateField('text', val)}
description="Enter the text"
multiline
rows={4}
inputProps={{
placeholder: 'Enter your text here'
}}
/>
)}
{values.qrCodeType === 'Email' && (
<>
<TextFieldWithDesc
value={values.emailAddress}
onOwnChange={(val) => updateField('emailAddress', val)}
description="Email Address"
inputProps={{
placeholder: 'example@example.com',
type: 'email'
}}
/>
<TextFieldWithDesc
value={values.emailSubject}
onOwnChange={(val) => updateField('emailSubject', val)}
description="Email Subject (optional)"
inputProps={{
placeholder: 'Subject line'
}}
/>
<TextFieldWithDesc
value={values.emailBody}
onOwnChange={(val) => updateField('emailBody', val)}
description="Email Body (optional)"
multiline
rows={4}
inputProps={{
placeholder: 'Body text'
}}
/>
</>
)}
{values.qrCodeType === 'Phone' && (
<TextFieldWithDesc
value={values.phoneNumber}
onOwnChange={(val) => updateField('phoneNumber', val)}
description="Phone Number"
inputProps={{
placeholder: '+1234567890',
type: 'tel'
}}
/>
)}
{values.qrCodeType === 'SMS' && (
<>
<TextFieldWithDesc
value={values.smsNumber}
onOwnChange={(val) => updateField('smsNumber', val)}
description="Phone Number"
inputProps={{
placeholder: '+1234567890',
type: 'tel'
}}
/>
<TextFieldWithDesc
value={values.smsMessage}
onOwnChange={(val) => updateField('smsMessage', val)}
description="Message (optional)"
multiline
rows={4}
inputProps={{
placeholder: 'Your message here'
}}
/>
</>
)}
{values.qrCodeType === 'WiFi' && (
<>
<TextFieldWithDesc
value={values.wifiSsid}
onOwnChange={(val) => updateField('wifiSsid', val)}
description="Network Name (SSID)"
inputProps={{
placeholder: 'Network name'
}}
/>
<TextFieldWithDesc
value={values.wifiPassword}
onOwnChange={(val) => updateField('wifiPassword', val)}
description="Password"
inputProps={{
placeholder: 'Password',
type: 'password'
}}
/>
<TextField
select
fullWidth
value={values.wifiEncryption}
onChange={(e) =>
updateField(
'wifiEncryption',
e.target.value as WifiEncryptionType
)
}
label="Encryption Type"
margin="normal"
>
<MenuItem value="WPA/WPA2">WPA/WPA2</MenuItem>
<MenuItem value="WEP">WEP</MenuItem>
<MenuItem value="None">None</MenuItem>
</TextField>
</>
)}
{values.qrCodeType === 'vCard' && (
<>
<TextFieldWithDesc
value={values.vCardName}
onOwnChange={(val) => updateField('vCardName', val)}
description="Full Name"
inputProps={{
placeholder: 'John Doe'
}}
/>
<TextFieldWithDesc
value={values.vCardEmail}
onOwnChange={(val) => updateField('vCardEmail', val)}
description="Email"
inputProps={{
placeholder: 'john@example.com',
type: 'email'
}}
/>
<TextFieldWithDesc
value={values.vCardPhone}
onOwnChange={(val) => updateField('vCardPhone', val)}
description="Phone"
inputProps={{
placeholder: '+1234567890',
type: 'tel'
}}
/>
<TextFieldWithDesc
value={values.vCardAddress}
onOwnChange={(val) => updateField('vCardAddress', val)}
description="Address"
inputProps={{
placeholder: '123 Main St, City, Country'
}}
/>
<TextFieldWithDesc
value={values.vCardCompany}
onOwnChange={(val) => updateField('vCardCompany', val)}
description="Company (optional)"
inputProps={{
placeholder: 'Company name'
}}
/>
<TextFieldWithDesc
value={values.vCardTitle}
onOwnChange={(val) => updateField('vCardTitle', val)}
description="Job Title (optional)"
inputProps={{
placeholder: 'Software Developer'
}}
/>
<TextFieldWithDesc
value={values.vCardWebsite}
onOwnChange={(val) => updateField('vCardWebsite', val)}
description="Website (optional)"
inputProps={{
placeholder: 'https://example.com'
}}
/>
</>
)}
</Box>
)
}
];
};
const compute = async (options: InitialValuesType) => {
const qrValue = formatQRCodeData(options);
if (!qrValue) return;
const canvas = document.createElement('canvas');
QRCode.toDataURL(
canvas,
qrValue,
{
color: {
dark: options.fgColor,
light: options.bgColor
},
width: Number(options.size) || 200
},
async (error, url) => {
const res = await fetch(url);
const blob = await res.blob();
const file = new File([blob], 'Qr code.png', { type: 'image/png' });
setResult(file);
}
);
};
const debouncedCompute = useCallback(debounce(compute, 1000), []);
return (
<ToolContent
title={title}
initialValues={initialValues}
getGroups={getGroups}
validationSchema={validationSchema}
compute={debouncedCompute}
resultComponent={
<ToolFileResult title={'Generated QR code'} value={result} />
}
toolInfo={{
title: 'QR Code Generator',
description:
'Generate QR codes for different data types: URL, Text, Email, Phone, SMS, WiFi, vCard, and more. Customize the size and colors to create the perfect QR code for your needs.'
}}
/>
);
}

View File

@@ -0,0 +1,25 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('image-generic', {
name: 'QR Code Generator',
path: 'qr-code',
icon: 'mdi:qrcode', // Iconify icon as a string
description:
'Generate QR codes for different data types: URL, Text, Email, Phone, SMS, WiFi, vCard, and more.',
shortDescription: 'Create customized QR codes for various data formats.',
keywords: [
'qr code',
'qrcode',
'generator',
'url',
'text',
'email',
'phone',
'sms',
'wifi',
'vcard',
'contact'
],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,51 @@
export type QRCodeType =
| 'URL'
| 'Text'
| 'Email'
| 'Phone'
| 'SMS'
| 'WiFi'
| 'vCard';
export type WifiEncryptionType = 'WPA/WPA2' | 'WEP' | 'None';
export interface InitialValuesType {
qrCodeType: QRCodeType;
// Common settings
size: string;
bgColor: string;
fgColor: string;
// URL
url: string;
// Text
text: string;
// Email
emailAddress: string;
emailSubject: string;
emailBody: string;
// Phone
phoneNumber: string;
// SMS
smsNumber: string;
smsMessage: string;
// WiFi
wifiSsid: string;
wifiPassword: string;
wifiEncryption: WifiEncryptionType;
// vCard
vCardName: string;
vCardEmail: string;
vCardPhone: string;
vCardAddress: string;
vCardCompany: string;
vCardTitle: string;
vCardWebsite: string;
}