mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-19 05:59:34 +02:00
Merge pull request #209 from AshAnand34/tools-filtering
Tools filtering
This commit is contained in:
386
.idea/workspace.xml
generated
386
.idea/workspace.xml
generated
@@ -4,9 +4,32 @@
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="chore: sync locize">
|
||||
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="chore: translate userTypes">
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/input/ToolCodeInput.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/input/ToolCodeInput.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/de/pdf.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/de/pdf.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/en/audio.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/en/audio.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/en/csv.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/en/csv.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/en/image.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/en/image.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/en/json.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/en/json.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/en/list.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/en/list.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/en/number.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/en/number.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/en/pdf.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/en/pdf.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/en/string.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/en/string.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/en/time.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/en/time.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/en/translation.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/en/translation.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/en/video.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/en/video.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/es/pdf.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/es/pdf.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/es/translation.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/es/translation.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/fr/pdf.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/fr/pdf.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/fr/time.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/fr/time.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/fr/translation.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/fr/translation.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/hi/json.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/hi/json.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/hi/pdf.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/hi/pdf.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/ja/pdf.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/ja/pdf.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/nl/pdf.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/nl/pdf.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/pt/pdf.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/pt/pdf.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/ru/pdf.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/ru/pdf.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/locales/zh/pdf.json" beforeDir="false" afterPath="$PROJECT_DIR$/public/locales/zh/pdf.json" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -24,10 +47,23 @@
|
||||
<option name="BRANCH" value="origin/main" />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<excluded-from-favorite>
|
||||
<branch-storage>
|
||||
<map>
|
||||
<entry type="LOCAL">
|
||||
<value>
|
||||
<list>
|
||||
<branch-info repo="$PROJECT_DIR$" source="main" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</branch-storage>
|
||||
</excluded-from-favorite>
|
||||
<option name="PUSH_AUTO_UPDATE" value="true" />
|
||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||
<map>
|
||||
<entry key="$PROJECT_DIR$" value="fork/bhavesh158/json-compare" />
|
||||
<entry key="$PROJECT_DIR$" value="main" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
@@ -40,198 +76,217 @@
|
||||
},
|
||||
{
|
||||
"state": "OPEN"
|
||||
},
|
||||
{
|
||||
"searchQuery": "filter",
|
||||
"state": "OPEN"
|
||||
}
|
||||
],
|
||||
"lastFilter": {
|
||||
"searchQuery": "filter",
|
||||
"state": "OPEN"
|
||||
}
|
||||
}</component>
|
||||
<component name="GitHubPullRequestState"><![CDATA[{
|
||||
"prStates": [
|
||||
<component name="GitHubPullRequestState">{
|
||||
"prStates": [
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts51PkS9",
|
||||
"number": 22
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts51PkS9",
|
||||
"number": 22
|
||||
},
|
||||
"lastSeen": 1741207144695
|
||||
"lastSeen": 1741207144695
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6NiNYl",
|
||||
"number": 32
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6NiNYl",
|
||||
"number": 32
|
||||
},
|
||||
"lastSeen": 1741209723869
|
||||
"lastSeen": 1741209723869
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6Nheyd",
|
||||
"number": 31
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6Nheyd",
|
||||
"number": 31
|
||||
},
|
||||
"lastSeen": 1741213371410
|
||||
"lastSeen": 1741213371410
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6NmRBs",
|
||||
"number": 33
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6NmRBs",
|
||||
"number": 33
|
||||
},
|
||||
"lastSeen": 1741282429036
|
||||
"lastSeen": 1741282429036
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts5zyFTs",
|
||||
"number": 15
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts5zyFTs",
|
||||
"number": 15
|
||||
},
|
||||
"lastSeen": 1741535540953
|
||||
"lastSeen": 1741535540953
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6QQB3c",
|
||||
"number": 59
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6QQB3c",
|
||||
"number": 59
|
||||
},
|
||||
"lastSeen": 1743018960900
|
||||
"lastSeen": 1743018960900
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6QMPEg",
|
||||
"number": 58
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6QMPEg",
|
||||
"number": 58
|
||||
},
|
||||
"lastSeen": 1743019452983
|
||||
"lastSeen": 1743019452983
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6QZvRI",
|
||||
"number": 61
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6QZvRI",
|
||||
"number": 61
|
||||
},
|
||||
"lastSeen": 1743103196866
|
||||
"lastSeen": 1743103196866
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6QqPrQ",
|
||||
"number": 73
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6QqPrQ",
|
||||
"number": 73
|
||||
},
|
||||
"lastSeen": 1743265865001
|
||||
"lastSeen": 1743265865001
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6Qp5nI",
|
||||
"number": 72
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6Qp5nI",
|
||||
"number": 72
|
||||
},
|
||||
"lastSeen": 1743338472110
|
||||
"lastSeen": 1743338472110
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6QsjlS",
|
||||
"number": 76
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6QsjlS",
|
||||
"number": 76
|
||||
},
|
||||
"lastSeen": 1743352150953
|
||||
"lastSeen": 1743352150953
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6Q0JBe",
|
||||
"number": 82
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6Q0JBe",
|
||||
"number": 82
|
||||
},
|
||||
"lastSeen": 1743470267269
|
||||
"lastSeen": 1743470267269
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6UE9-x",
|
||||
"number": 102
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6UE9-x",
|
||||
"number": 102
|
||||
},
|
||||
"lastSeen": 1747171977348
|
||||
"lastSeen": 1747171977348
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6XPua_",
|
||||
"number": 117
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6XPua_",
|
||||
"number": 117
|
||||
},
|
||||
"lastSeen": 1747929835864
|
||||
"lastSeen": 1747929835864
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6XY-mZ",
|
||||
"number": 119
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6XY-mZ",
|
||||
"number": 119
|
||||
},
|
||||
"lastSeen": 1748028108508
|
||||
"lastSeen": 1748028108508
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6Xdz4n",
|
||||
"number": 120
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6Xdz4n",
|
||||
"number": 120
|
||||
},
|
||||
"lastSeen": 1748282672214
|
||||
"lastSeen": 1748282672214
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6X_zxl",
|
||||
"number": 131
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6X_zxl",
|
||||
"number": 131
|
||||
},
|
||||
"lastSeen": 1748881279494
|
||||
"lastSeen": 1748881279494
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6bhieT",
|
||||
"number": 152
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6bhieT",
|
||||
"number": 152
|
||||
},
|
||||
"lastSeen": 1751848489082
|
||||
"lastSeen": 1751848489082
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6dOyRk",
|
||||
"number": 154
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6dOyRk",
|
||||
"number": 154
|
||||
},
|
||||
"lastSeen": 1751849436454
|
||||
"lastSeen": 1751849436454
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6cHjNi",
|
||||
"number": 153
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6cHjNi",
|
||||
"number": 153
|
||||
},
|
||||
"lastSeen": 1751849501498
|
||||
"lastSeen": 1751849501498
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6Zs1FN",
|
||||
"number": 145
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6Zs1FN",
|
||||
"number": 145
|
||||
},
|
||||
"lastSeen": 1751849770308
|
||||
"lastSeen": 1751849770308
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6bgKi9",
|
||||
"number": 150
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6bgKi9",
|
||||
"number": 150
|
||||
},
|
||||
"lastSeen": 1751850367300
|
||||
"lastSeen": 1751850367300
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6eUKC-",
|
||||
"number": 176
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6eUKC-",
|
||||
"number": 176
|
||||
},
|
||||
"lastSeen": 1752158748013
|
||||
"lastSeen": 1752158748013
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6eqzP7",
|
||||
"number": 190
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6eqzP7",
|
||||
"number": 190
|
||||
},
|
||||
"lastSeen": 1752404173008
|
||||
"lastSeen": 1752404173008
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6et6vx",
|
||||
"number": 192
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6et6vx",
|
||||
"number": 192
|
||||
},
|
||||
"lastSeen": 1752585709582
|
||||
"lastSeen": 1752585709582
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6d36mi",
|
||||
"number": 168
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6d36mi",
|
||||
"number": 168
|
||||
},
|
||||
"lastSeen": 1752805763664
|
||||
"lastSeen": 1752805763664
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6fnXKf",
|
||||
"number": 208
|
||||
},
|
||||
"lastSeen": 1752862212326
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6fo_ig",
|
||||
"number": 209
|
||||
},
|
||||
"lastSeen": 1753201966322
|
||||
}
|
||||
]
|
||||
}]]></component>
|
||||
}</component>
|
||||
<component name="GithubPullRequestsUISettings">{
|
||||
"selectedUrlAndAccountId": {
|
||||
"url": "https://github.com/iib0011/omni-tools.git",
|
||||
@@ -291,7 +346,7 @@
|
||||
"Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run",
|
||||
"Vitest.replaceText function.executor": "Run",
|
||||
"Vitest.timeBetweenDates.executor": "Run",
|
||||
"git-widget-placeholder": "main",
|
||||
"git-widget-placeholder": "#209 on fork/AshAnand34/tools-filtering",
|
||||
"ignore.virus.scanning.warn.message": "true",
|
||||
"kotlin-language-version-configured": "true",
|
||||
"last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools",
|
||||
@@ -316,8 +371,9 @@
|
||||
"project.structure.last.edited": "Problems",
|
||||
"project.structure.proportion": "0.0",
|
||||
"project.structure.side.proportion": "0.2",
|
||||
"settings.editor.selected.configurable": "refactai_advanced_settings",
|
||||
"settings.editor.selected.configurable": "preferences.pluginManager",
|
||||
"ts.external.directory.path": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib",
|
||||
"ts.rename.search.for.js.occurrences": "false",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}]]></component>
|
||||
@@ -346,7 +402,7 @@
|
||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\categories" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunManager" selected="npm.dev">
|
||||
<component name="RunManager" selected="npm.i18n:sync">
|
||||
<configuration name="generatePassword" type="JavaScriptTestRunnerVitest" temporary="true" nameIsGenerated="true">
|
||||
<node-interpreter value="project" />
|
||||
<vitest-package value="$PROJECT_DIR$/node_modules/vitest" />
|
||||
@@ -420,11 +476,11 @@
|
||||
</list>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="npm.dev" />
|
||||
<item itemvalue="npm.i18n:sync" />
|
||||
<item itemvalue="npm.dev" />
|
||||
<item itemvalue="Vitest.generatePassword" />
|
||||
<item itemvalue="npm.i18n:pull" />
|
||||
<item itemvalue="npm.i18n:extract" />
|
||||
<item itemvalue="Vitest.generatePassword" />
|
||||
</list>
|
||||
</recent_temporary>
|
||||
</component>
|
||||
@@ -538,46 +594,12 @@
|
||||
<workItem from="1752403829295" duration="13253000" />
|
||||
<workItem from="1752493585622" duration="11629000" />
|
||||
<workItem from="1752507105323" duration="9008000" />
|
||||
</task>
|
||||
<task id="LOCAL-00196" summary="chore: revert create-tool.mjs">
|
||||
<option name="closed" value="true" />
|
||||
<created>1748027090253</created>
|
||||
<option name="number" value="00196" />
|
||||
<option name="presentableId" value="LOCAL-00196" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1748027090253</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00197" summary="fix: misc">
|
||||
<option name="closed" value="true" />
|
||||
<created>1748027889103</created>
|
||||
<option name="number" value="00197" />
|
||||
<option name="presentableId" value="LOCAL-00197" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1748027889103</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00198" summary="chore: remove unnecessary prop">
|
||||
<option name="closed" value="true" />
|
||||
<created>1748028055669</created>
|
||||
<option name="number" value="00198" />
|
||||
<option name="presentableId" value="LOCAL-00198" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1748028055669</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00199" summary="fix: compute flow">
|
||||
<option name="closed" value="true" />
|
||||
<created>1748881153433</created>
|
||||
<option name="number" value="00199" />
|
||||
<option name="presentableId" value="LOCAL-00199" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1748881153433</updated>
|
||||
</task>
|
||||
<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>
|
||||
<workItem from="1752875788332" duration="3566000" />
|
||||
<workItem from="1753099267173" duration="21000" />
|
||||
<workItem from="1753123130080" duration="4054000" />
|
||||
<workItem from="1753201599796" duration="4449000" />
|
||||
<workItem from="1753206561770" duration="119000" />
|
||||
<workItem from="1753206717510" duration="3599000" />
|
||||
</task>
|
||||
<task id="LOCAL-00201" summary="chore: rename from Omni Tools to OmniTools">
|
||||
<option name="closed" value="true" />
|
||||
@@ -931,7 +953,47 @@
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1752805853344</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="245" />
|
||||
<task id="LOCAL-00245" summary="feat: language browser detection">
|
||||
<option name="closed" value="true" />
|
||||
<created>1753124389709</created>
|
||||
<option name="number" value="00245" />
|
||||
<option name="presentableId" value="LOCAL-00245" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1753124389709</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00246" summary="fix: misc">
|
||||
<option name="closed" value="true" />
|
||||
<created>1753206794968</created>
|
||||
<option name="number" value="00246" />
|
||||
<option name="presentableId" value="LOCAL-00246" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1753206794968</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00247" summary="chore: show only necessary tags on a category">
|
||||
<option name="closed" value="true" />
|
||||
<created>1753207817041</created>
|
||||
<option name="number" value="00247" />
|
||||
<option name="presentableId" value="LOCAL-00247" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1753207817041</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00248" summary="chore: CATEGORIES_USER_TYPES_MAPPINGS">
|
||||
<option name="closed" value="true" />
|
||||
<created>1753209484099</created>
|
||||
<option name="number" value="00248" />
|
||||
<option name="presentableId" value="LOCAL-00248" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1753209484099</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00249" summary="chore: translate userTypes">
|
||||
<option name="closed" value="true" />
|
||||
<created>1753210033390</created>
|
||||
<option name="number" value="00249" />
|
||||
<option name="presentableId" value="LOCAL-00249" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1753210033390</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="250" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
@@ -978,11 +1040,6 @@
|
||||
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
||||
<option name="CHECK_NEW_TODO" value="false" />
|
||||
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
|
||||
<MESSAGE value="chore: style link" />
|
||||
<MESSAGE value="refactor: PDF editor" />
|
||||
<MESSAGE value="docs: edit pdf meta" />
|
||||
<MESSAGE value="fix: misc" />
|
||||
<MESSAGE value="chore: locize upload" />
|
||||
<MESSAGE value="chore: i18n in meta" />
|
||||
<MESSAGE value="chore: add i18n to meta script" />
|
||||
<MESSAGE value="chore: bundle translations at build time" />
|
||||
@@ -1003,7 +1060,12 @@
|
||||
<MESSAGE value="fix: i18n tsc" />
|
||||
<MESSAGE value="chore: i18n pull dutch" />
|
||||
<MESSAGE value="chore: sync locize" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="chore: sync locize" />
|
||||
<MESSAGE value="feat: language browser detection" />
|
||||
<MESSAGE value="fix: misc" />
|
||||
<MESSAGE value="chore: show only necessary tags on a category" />
|
||||
<MESSAGE value="chore: CATEGORIES_USER_TYPES_MAPPINGS" />
|
||||
<MESSAGE value="chore: translate userTypes" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="chore: translate userTypes" />
|
||||
</component>
|
||||
<component name="VgoProject">
|
||||
<integration-enabled>false</integration-enabled>
|
||||
|
@@ -11,6 +11,7 @@
|
||||
"highCompression": "High Compression",
|
||||
"highCompressionDescription": "Maximum file size reduction with some quality loss",
|
||||
"inputTitle": "Input PDF",
|
||||
"longDescription": "Compress PDF files securely in your browser using Ghostscript. Your files never leave your device, ensuring complete privacy while reducing file sizes for email sharing, uploading to websites, or saving storage space. Powered by WebAssembly technology.",
|
||||
"lowCompression": "Low Compression",
|
||||
"lowCompressionDescription": "Slightly reduce file size with minimal quality loss",
|
||||
"mediumCompression": "Medium Compression",
|
||||
|
@@ -258,30 +258,29 @@
|
||||
"shortDescription": "Convert text to uppercase",
|
||||
"title": "Convert to Uppercase"
|
||||
},
|
||||
"urlEncode": {
|
||||
"toolInfo": {
|
||||
"description": "Load your string and it will automatically get URL-escaped.",
|
||||
"shortDescription": "Quickly URL-escape a string.",
|
||||
"longDescription": "This tool URL-encodes a string. Special URL characters get converted to percent-sign encoding. This encoding is called percent-encoding because each character's numeric value gets converted to a percent sign followed by a two-digit hexadecimal value. The hex values are determined based on the character's codepoint value. For example, a space gets escaped to %20, a colon to %3a, a slash to %2f. Characters that are not special stay unchanged. In case you also need to convert non-special characters to percent-encoding, then we've also added an extra option that lets you do that. Select the encode-non-special-chars option to enable this behavior.",
|
||||
"title": "String URL encoder"
|
||||
},
|
||||
"encodingOption": {
|
||||
"title": "Encoding Options",
|
||||
"nonSpecialCharPlaceholder": "Encode non-special characters",
|
||||
"nonSpecialCharDescription": "If selected, then all characters in the input string will be converted to URL-encoding (not just special)."
|
||||
},
|
||||
"inputTitle": "Input String",
|
||||
"resultTitle": "Url-escaped String"
|
||||
},
|
||||
"urlDecode": {
|
||||
"inputTitle": "Input String(URL-escaped)",
|
||||
"resultTitle": "Output string",
|
||||
"toolInfo": {
|
||||
"description": "Load your string and it will automatically get URL-unescaped.",
|
||||
"shortDescription": "Quickly URL-unescape a string.",
|
||||
"longDescription": "This tool URL-decodes a previously URL-encoded string. URL-decoding is the inverse operation of URL-encoding. All percent-encoded characters get decoded to characters that you can understand. Some of the most well known percent-encoded values are %20 for a space, %3a for a colon, %2f for a slash, and %3f for a question mark. The two digits following the percent sign are character's char code values in hex.",
|
||||
"shortDescription": "Quickly URL-unescape a string.",
|
||||
"title": "String URL decoder"
|
||||
}
|
||||
},
|
||||
|
||||
"inputTitle": "Input String(URL-escaped)",
|
||||
"resultTitle": "Output string"
|
||||
"urlEncode": {
|
||||
"encodingOption": {
|
||||
"nonSpecialCharDescription": "If selected, then all characters in the input string will be converted to URL-encoding (not just special).",
|
||||
"nonSpecialCharPlaceholder": "Encode non-special characters",
|
||||
"title": "Encoding Options"
|
||||
},
|
||||
"inputTitle": "Input String",
|
||||
"resultTitle": "Url-escaped String",
|
||||
"toolInfo": {
|
||||
"description": "Load your string and it will automatically get URL-escaped.",
|
||||
"longDescription": "This tool URL-encodes a string. Special URL characters get converted to percent-sign encoding. This encoding is called percent-encoding because each character's numeric value gets converted to a percent sign followed by a two-digit hexadecimal value. The hex values are determined based on the character's codepoint value. For example, a space gets escaped to %20, a colon to %3a, a slash to %2f. Characters that are not special stay unchanged. In case you also need to convert non-special characters to percent-encoding, then we've also added an extra option that lets you do that. Select the encode-non-special-chars option to enable this behavior.",
|
||||
"shortDescription": "Quickly URL-escape a string.",
|
||||
"title": "String URL encoder"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -58,6 +58,21 @@
|
||||
"title": "Convert Time to Seconds"
|
||||
}
|
||||
},
|
||||
"convertUnixToDate": {
|
||||
"addUtcLabel": "Add 'UTC' suffix",
|
||||
"addUtcLabelDescription": "Display 'UTC' after the converted date (only for UTC mode)",
|
||||
"description": "Convert a Unix timestamp to a human-readable date.",
|
||||
"outputOptions": "Output Options",
|
||||
"shortDescription": "Convert Unix timestamp to date",
|
||||
"title": "Convert Unix to Date",
|
||||
"toolInfo": {
|
||||
"description": "This tool converts a Unix timestamp (in seconds) into a human-readable date format (e.g., YYYY-MM-DD HH:MM:SS). It supports both local and UTC output, making it useful for quickly interpreting timestamps from logs, APIs, or systems that use Unix time.",
|
||||
"title": "Convert Unix to Date"
|
||||
},
|
||||
"useLocalTime": "Use Local Time",
|
||||
"useLocalTimeDescription": "Show converted date in your local timezone instead of UTC",
|
||||
"withLabel": "Options"
|
||||
},
|
||||
"crontabGuru": {
|
||||
"description": "Generate and understand cron expressions. Create cron schedules for automated tasks and system jobs.",
|
||||
"shortDescription": "Generate and understand cron expressions",
|
||||
@@ -98,21 +113,5 @@
|
||||
"zeroPaddingDescription": "Make all time components always be two digits wide.",
|
||||
"zeroPrintDescription": "Display the dropped parts as zero values \"00\".",
|
||||
"zeroPrintTruncatedParts": "Zero-print Truncated Parts"
|
||||
},
|
||||
"convertUnixToDate": {
|
||||
"title": "Convert Unix to Date",
|
||||
"description": "Convert a Unix timestamp to a human-readable date.",
|
||||
"shortDescription": "Convert Unix timestamp to date",
|
||||
"longDescription": "",
|
||||
"withLabel": "Options",
|
||||
"outputOptions": "Output Options",
|
||||
"addUtcLabel": "Add 'UTC' suffix",
|
||||
"addUtcLabelDescription": "Display 'UTC' after the converted date (only for UTC mode)",
|
||||
"useLocalTime": "Use Local Time",
|
||||
"useLocalTimeDescription": "Show converted date in your local timezone instead of UTC",
|
||||
"toolInfo": {
|
||||
"title": "Convert Unix to Date",
|
||||
"description": "This tool converts a Unix timestamp (in seconds) into a human-readable date format (e.g., YYYY-MM-DD HH:MM:SS). It supports both local and UTC output, making it useful for quickly interpreting timestamps from logs, APIs, or systems that use Unix time."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -246,5 +246,9 @@
|
||||
"copyFailed": "Failed to copy: {{error}}",
|
||||
"loading": "Loading... This may take a moment.",
|
||||
"result": "Result"
|
||||
},
|
||||
"userTypes": {
|
||||
"developers": "Developers",
|
||||
"generalUsers": "General users"
|
||||
}
|
||||
}
|
||||
|
@@ -38,6 +38,7 @@
|
||||
"height": "Height",
|
||||
"inputTitle": "Input Video",
|
||||
"loadVideoForDimensions": "Load a video to see dimensions",
|
||||
"longDescription": "This tool allows you to crop video files to remove unwanted areas or focus on specific parts of the video. Useful for removing black bars, adjusting aspect ratios, or focusing on important content. Supports various video formats including MP4, MOV, and AVI.",
|
||||
"resultTitle": "Cropped Video",
|
||||
"shortDescription": "Crop video to remove unwanted areas",
|
||||
"title": "Crop Video",
|
||||
|
@@ -246,5 +246,9 @@
|
||||
"copyFailed": "No se pudo copiar: {{error}}",
|
||||
"loading": "Cargando... Esto puede tardar un momento.",
|
||||
"result": "Resultado"
|
||||
},
|
||||
"userTypes": {
|
||||
"developers": "Desarrolladores",
|
||||
"generalUsers": "Usuarios generales"
|
||||
}
|
||||
}
|
||||
|
@@ -56,6 +56,22 @@
|
||||
"title": "Convertir le temps en secondes"
|
||||
}
|
||||
},
|
||||
"convertUnixToDate": {
|
||||
"addUtcLabel": "Ajouter le suffixe 'UTC'",
|
||||
"addUtcLabelDescription": "Affiche 'UTC' après la date convertie (uniquement en mode UTC)",
|
||||
"description": "Convertit un timestamp Unix en une date lisible par un humain.",
|
||||
"longDescription": "Cet outil permet de convertir un timestamp Unix (en secondes) en une date lisible au format AAAA-MM-JJ HH:MM:SS. Il prend en charge l'affichage en UTC ou dans le fuseau horaire local, ce qui est pratique pour interpréter rapidement des horodatages issus de journaux, d'API ou de systèmes utilisant le temps Unix.",
|
||||
"outputOptions": "Options de sortie",
|
||||
"shortDescription": "Conversion de timestamp Unix en date",
|
||||
"title": "Convertir un timestamp Unix en date",
|
||||
"toolInfo": {
|
||||
"description": "Cet outil convertit un timestamp Unix (en secondes) en une date lisible (par ex. AAAA-MM-JJ HH:MM:SS). Il prend en charge l'affichage en heure locale ou en UTC, ce qui le rend utile pour analyser rapidement des données issues de journaux ou d’APIs.",
|
||||
"title": "Convertir un timestamp Unix en date"
|
||||
},
|
||||
"useLocalTime": "Utiliser l’heure locale",
|
||||
"useLocalTimeDescription": "Affiche la date convertie dans votre fuseau horaire local au lieu de l’heure UTC",
|
||||
"withLabel": "Options"
|
||||
},
|
||||
"crontabGuru": {
|
||||
"description": "Générez et comprenez les expressions Cron. Créez des planifications Cron pour les tâches automatisées et les tâches système.",
|
||||
"shortDescription": "Générer et comprendre les expressions cron",
|
||||
@@ -96,21 +112,5 @@
|
||||
"zeroPaddingDescription": "Faites en sorte que tous les composants de temps aient toujours une largeur de deux chiffres.",
|
||||
"zeroPrintDescription": "Afficher les parties supprimées sous forme de valeurs nulles « 00 ».",
|
||||
"zeroPrintTruncatedParts": "Parties tronquées sans impression"
|
||||
},
|
||||
"convertUnixToDate": {
|
||||
"title": "Convertir un timestamp Unix en date",
|
||||
"description": "Convertit un timestamp Unix en une date lisible par un humain.",
|
||||
"shortDescription": "Conversion de timestamp Unix en date",
|
||||
"longDescription": "Cet outil permet de convertir un timestamp Unix (en secondes) en une date lisible au format AAAA-MM-JJ HH:MM:SS. Il prend en charge l'affichage en UTC ou dans le fuseau horaire local, ce qui est pratique pour interpréter rapidement des horodatages issus de journaux, d'API ou de systèmes utilisant le temps Unix.",
|
||||
"withLabel": "Options",
|
||||
"outputOptions": "Options de sortie",
|
||||
"addUtcLabel": "Ajouter le suffixe 'UTC'",
|
||||
"addUtcLabelDescription": "Affiche 'UTC' après la date convertie (uniquement en mode UTC)",
|
||||
"useLocalTime": "Utiliser l’heure locale",
|
||||
"useLocalTimeDescription": "Affiche la date convertie dans votre fuseau horaire local au lieu de l’heure UTC",
|
||||
"toolInfo": {
|
||||
"title": "Convertir un timestamp Unix en date",
|
||||
"description": "Cet outil convertit un timestamp Unix (en secondes) en une date lisible (par ex. AAAA-MM-JJ HH:MM:SS). Il prend en charge l'affichage en heure locale ou en UTC, ce qui le rend utile pour analyser rapidement des données issues de journaux ou d’APIs."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -246,5 +246,9 @@
|
||||
"copyFailed": "Échec de la copie : {{error}}",
|
||||
"loading": "Chargement... Cela peut prendre un moment.",
|
||||
"result": "Résultat"
|
||||
},
|
||||
"userTypes": {
|
||||
"developers": "Développeurs",
|
||||
"generalUsers": "Grand public"
|
||||
}
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ import { darkTheme, lightTheme } from '../config/muiConfig';
|
||||
import ScrollToTopButton from './ScrollToTopButton';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import i18n from '../i18n';
|
||||
import { UserTypeFilterProvider } from 'providers/UserTypeFilterProvider';
|
||||
|
||||
export type Mode = 'dark' | 'light' | 'system';
|
||||
|
||||
@@ -57,6 +58,7 @@ function App() {
|
||||
}}
|
||||
>
|
||||
<CustomSnackBarProvider>
|
||||
<UserTypeFilterProvider>
|
||||
<BrowserRouter>
|
||||
<Navbar
|
||||
mode={mode}
|
||||
@@ -69,6 +71,7 @@ function App() {
|
||||
<AppRoutes />
|
||||
</Suspense>
|
||||
</BrowserRouter>
|
||||
</UserTypeFilterProvider>
|
||||
</CustomSnackBarProvider>
|
||||
</SnackbarProvider>
|
||||
<ScrollToTopButton />
|
||||
|
@@ -25,6 +25,7 @@ import {
|
||||
toggleBookmarked
|
||||
} from '@utils/bookmark';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import { useUserTypeFilter } from '../providers/UserTypeFilterProvider';
|
||||
|
||||
const GroupHeader = styled('div')(({ theme }) => ({
|
||||
position: 'sticky',
|
||||
@@ -50,6 +51,7 @@ export default function Hero() {
|
||||
const { t } = useTranslation(validNamespaces);
|
||||
const [inputValue, setInputValue] = useState<string>('');
|
||||
const theme = useTheme();
|
||||
const { selectedUserTypes } = useUserTypeFilter();
|
||||
const [filteredTools, setFilteredTools] = useState<DefinedTool[]>(tools);
|
||||
const [bookmarkedToolPaths, setBookmarkedToolPaths] = useState<string[]>(
|
||||
getBookmarkedToolPaths()
|
||||
@@ -96,12 +98,13 @@ export default function Hero() {
|
||||
];
|
||||
|
||||
const handleInputChange = (
|
||||
event: React.ChangeEvent<{}>,
|
||||
_event: React.ChangeEvent<{}>,
|
||||
newInputValue: string
|
||||
) => {
|
||||
setInputValue(newInputValue);
|
||||
setFilteredTools(filterTools(tools, newInputValue, t));
|
||||
setFilteredTools(filterTools(tools, newInputValue, selectedUserTypes, t));
|
||||
};
|
||||
|
||||
const toolsMap = new Map<string, ToolInfo>();
|
||||
for (const tool of filteredTools) {
|
||||
toolsMap.set(tool.path, {
|
||||
|
@@ -103,7 +103,7 @@ export default function ToolHeader({
|
||||
items={[
|
||||
{ title: 'All tools', link: '/' },
|
||||
{
|
||||
title: getToolsByCategory(t).find(
|
||||
title: getToolsByCategory([], t).find(
|
||||
(category) => category.type === type
|
||||
)!.rawTitle,
|
||||
link: '/categories/' + type
|
||||
|
@@ -43,7 +43,7 @@ export default function ToolLayout({
|
||||
const toolDescription: string = t(i18n.description);
|
||||
|
||||
const otherCategoryTools =
|
||||
getToolsByCategory(t)
|
||||
getToolsByCategory([], t)
|
||||
.find((category) => category.type === type)
|
||||
?.tools.filter((tool) => t(tool.name) !== toolTitle)
|
||||
.map((tool) => ({
|
||||
@@ -77,8 +77,9 @@ export default function ToolLayout({
|
||||
<AllTools
|
||||
title={t('translation:toolLayout.allToolsTitle', '', {
|
||||
type: capitalizeFirstLetter(
|
||||
getToolsByCategory(t).find((category) => category.type === type)!
|
||||
.title
|
||||
getToolsByCategory([], t).find(
|
||||
(category) => category.type === type
|
||||
)!.title
|
||||
)
|
||||
})}
|
||||
toolCards={otherCategoryTools}
|
||||
|
47
src/components/UserTypeFilter.tsx
Normal file
47
src/components/UserTypeFilter.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import { Box, Chip } from '@mui/material';
|
||||
import { UserType } from '@tools/defineTool';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface UserTypeFilterProps {
|
||||
selectedUserTypes: UserType[];
|
||||
userTypes?: UserType[];
|
||||
onUserTypesChange: (userTypes: UserType[]) => void;
|
||||
}
|
||||
|
||||
export default function UserTypeFilter({
|
||||
selectedUserTypes,
|
||||
onUserTypesChange,
|
||||
userTypes = ['generalUsers', 'developers']
|
||||
}: UserTypeFilterProps) {
|
||||
const { t } = useTranslation('translation');
|
||||
if (userTypes.length <= 1) return null;
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: 1,
|
||||
minWidth: 200,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
{userTypes.map((userType) => (
|
||||
<Chip
|
||||
key={userType}
|
||||
label={t(`userTypes.${userType}`)}
|
||||
color={selectedUserTypes.includes(userType) ? 'primary' : 'default'}
|
||||
onClick={() => {
|
||||
const isSelected = selectedUserTypes.includes(userType);
|
||||
const newUserTypes = isSelected
|
||||
? selectedUserTypes.filter((ut) => ut !== userType)
|
||||
: [...selectedUserTypes, userType];
|
||||
onUserTypesChange(newUserTypes);
|
||||
}}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -9,7 +9,7 @@ import { categoriesColors } from 'config/uiConfig';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getI18nNamespaceFromToolCategory } from '@utils/string';
|
||||
import { validNamespaces } from '../../i18n';
|
||||
import { useUserTypeFilter } from '../../providers/UserTypeFilterProvider';
|
||||
|
||||
type ArrayElement<ArrayType extends readonly unknown[]> =
|
||||
ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
|
||||
@@ -84,10 +84,11 @@ const SingleCategory = function ({
|
||||
</Stack>
|
||||
<Typography sx={{ mt: 2 }}>{categoryDescription}</Typography>
|
||||
</Box>
|
||||
<Grid mt={1} container spacing={2}>
|
||||
<Grid container spacing={2} mt={2}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Button
|
||||
fullWidth
|
||||
sx={{ height: '100%' }}
|
||||
onClick={() => navigate('/categories/' + category.type)}
|
||||
variant={'contained'}
|
||||
>
|
||||
@@ -96,7 +97,7 @@ const SingleCategory = function ({
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Button
|
||||
sx={{ backgroundColor: 'background.default' }}
|
||||
sx={{ backgroundColor: 'background.default', height: '100%' }}
|
||||
fullWidth
|
||||
onClick={() => navigate(category.example.path)}
|
||||
variant={'outlined'}
|
||||
@@ -111,11 +112,15 @@ const SingleCategory = function ({
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default function Categories() {
|
||||
const { selectedUserTypes } = useUserTypeFilter();
|
||||
const { t } = useTranslation();
|
||||
const categories = getToolsByCategory(selectedUserTypes, t);
|
||||
|
||||
return (
|
||||
<Grid width={'80%'} container mt={2} spacing={2}>
|
||||
{getToolsByCategory(t).map((category, index) => (
|
||||
<Grid width={'80%'} container spacing={2}>
|
||||
{categories.map((category, index) => (
|
||||
<SingleCategory key={category.type} category={category} index={index} />
|
||||
))}
|
||||
</Grid>
|
||||
|
@@ -2,9 +2,12 @@ import { Box, useTheme } from '@mui/material';
|
||||
import Hero from 'components/Hero';
|
||||
import Categories from './Categories';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { useUserTypeFilter } from 'providers/UserTypeFilterProvider';
|
||||
import UserTypeFilter from '@components/UserTypeFilter';
|
||||
|
||||
export default function Home() {
|
||||
const theme = useTheme();
|
||||
const { selectedUserTypes, setSelectedUserTypes } = useUserTypeFilter();
|
||||
return (
|
||||
<Box
|
||||
padding={{
|
||||
@@ -28,6 +31,12 @@ export default function Home() {
|
||||
>
|
||||
<Helmet title={'OmniTools'} />
|
||||
<Hero />
|
||||
<Box my={3}>
|
||||
<UserTypeFilter
|
||||
selectedUserTypes={selectedUserTypes}
|
||||
onUserTypesChange={setSelectedUserTypes}
|
||||
/>
|
||||
</Box>
|
||||
<Categories />
|
||||
</Box>
|
||||
);
|
||||
|
@@ -22,28 +22,38 @@ import IconButton from '@mui/material/IconButton';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import UserTypeFilter from '@components/UserTypeFilter';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { I18nNamespaces, validNamespaces } from '../../i18n';
|
||||
import { useUserTypeFilter } from '../../providers/UserTypeFilterProvider';
|
||||
|
||||
const StyledLink = styled(Link)(({ theme }) => ({
|
||||
'&:hover': {
|
||||
color: theme.palette.mode === 'dark' ? 'white' : theme.palette.primary.light
|
||||
}
|
||||
}));
|
||||
|
||||
export default function ToolsByCategory() {
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const mainContentRef = React.useRef<HTMLDivElement>(null);
|
||||
const { categoryName } = useParams();
|
||||
const [searchTerm, setSearchTerm] = React.useState<string>('');
|
||||
const { selectedUserTypes, setSelectedUserTypes } = useUserTypeFilter();
|
||||
const { t } = useTranslation(validNamespaces);
|
||||
const rawTitle = getToolCategoryTitle(categoryName as string, t);
|
||||
// First get tools by category without filtering
|
||||
const toolsByCategory =
|
||||
getToolsByCategory(t).find(({ type }) => type === categoryName)?.tools ??
|
||||
[];
|
||||
const toolsByCategory = getToolsByCategory(selectedUserTypes, t).find(
|
||||
({ type }) => type === categoryName
|
||||
);
|
||||
const categoryDefinedTools = toolsByCategory?.tools ?? [];
|
||||
|
||||
const categoryTools = filterTools(toolsByCategory, searchTerm, t);
|
||||
const categoryTools = filterTools(
|
||||
categoryDefinedTools,
|
||||
searchTerm,
|
||||
selectedUserTypes,
|
||||
t
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (mainContentRef.current) {
|
||||
@@ -90,7 +100,20 @@ export default function ToolsByCategory() {
|
||||
onChange={(event) => setSearchTerm(event.target.value)}
|
||||
/>
|
||||
</Stack>
|
||||
<Grid container spacing={2} mt={2}>
|
||||
<Box
|
||||
width={'100%'}
|
||||
display={'flex'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
my={2}
|
||||
>
|
||||
<UserTypeFilter
|
||||
userTypes={toolsByCategory?.userTypes ?? undefined}
|
||||
selectedUserTypes={selectedUserTypes}
|
||||
onUserTypesChange={setSelectedUserTypes}
|
||||
/>
|
||||
</Box>
|
||||
<Grid container spacing={2}>
|
||||
{categoryTools.map((tool, index) => (
|
||||
<Grid item xs={12} md={6} lg={4} key={tool.path}>
|
||||
<Stack
|
||||
|
@@ -20,6 +20,7 @@ export const tool = defineTool('audio', {
|
||||
i18n: {
|
||||
name: 'audio:changeSpeed.title',
|
||||
description: 'audio:changeSpeed.description',
|
||||
shortDescription: 'audio:changeSpeed.shortDescription'
|
||||
shortDescription: 'audio:changeSpeed.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -21,6 +21,7 @@ export const tool = defineTool('audio', {
|
||||
i18n: {
|
||||
name: 'audio:extractAudio.title',
|
||||
description: 'audio:extractAudio.description',
|
||||
shortDescription: 'audio:extractAudio.shortDescription'
|
||||
shortDescription: 'audio:extractAudio.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -6,7 +6,8 @@ export const tool = defineTool('audio', {
|
||||
name: 'audio:mergeAudio.title',
|
||||
description: 'audio:mergeAudio.description',
|
||||
shortDescription: 'audio:mergeAudio.shortDescription',
|
||||
longDescription: 'audio:mergeAudio.longDescription'
|
||||
longDescription: 'audio:mergeAudio.longDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
},
|
||||
|
||||
path: 'merge-audio',
|
||||
@@ -24,6 +25,5 @@ export const tool = defineTool('audio', {
|
||||
'audio editing',
|
||||
'multiple files'
|
||||
],
|
||||
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
||||
|
@@ -6,7 +6,8 @@ export const tool = defineTool('audio', {
|
||||
name: 'audio:trim.title',
|
||||
description: 'audio:trim.description',
|
||||
shortDescription: 'audio:trim.shortDescription',
|
||||
longDescription: 'audio:trim.longDescription'
|
||||
longDescription: 'audio:trim.longDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
},
|
||||
|
||||
path: 'trim',
|
||||
@@ -24,6 +25,5 @@ export const tool = defineTool('audio', {
|
||||
'audio editing',
|
||||
'time'
|
||||
],
|
||||
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
||||
|
@@ -12,6 +12,5 @@ export const tool = defineTool('csv', {
|
||||
path: 'csv-to-yaml',
|
||||
icon: 'nonicons:yaml-16',
|
||||
keywords: ['csv', 'to', 'yaml'],
|
||||
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
||||
|
@@ -12,6 +12,5 @@ export const tool = defineTool('csv', {
|
||||
icon: 'hugeicons:column-insert',
|
||||
|
||||
keywords: ['insert', 'csv', 'columns', 'append', 'prepend'],
|
||||
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
||||
|
@@ -13,6 +13,5 @@ export const tool = defineTool('csv', {
|
||||
icon: 'carbon:transpose',
|
||||
|
||||
keywords: ['transpose', 'csv'],
|
||||
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
||||
|
@@ -5,12 +5,13 @@ export const tool = defineTool('image-generic', {
|
||||
i18n: {
|
||||
name: 'image:compress.title',
|
||||
description: 'image:compress.description',
|
||||
shortDescription: 'image:compress.shortDescription'
|
||||
shortDescription: 'image:compress.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
},
|
||||
|
||||
path: 'compress',
|
||||
component: lazy(() => import('./index')),
|
||||
icon: 'material-symbols-light:compress-rounded',
|
||||
|
||||
keywords: ['image', 'compress', 'reduce', 'quality']
|
||||
keywords: ['image', 'compress', 'reduce', 'quality'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
||||
|
@@ -5,7 +5,8 @@ export const tool = defineTool('image-generic', {
|
||||
i18n: {
|
||||
name: 'image:resize.title',
|
||||
description: 'image:resize.description',
|
||||
shortDescription: 'image:resize.shortDescription'
|
||||
shortDescription: 'image:resize.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
},
|
||||
|
||||
path: 'resize',
|
||||
|
@@ -2,7 +2,7 @@ import { defineTool } from '@tools/defineTool';
|
||||
import { lazy } from 'react';
|
||||
|
||||
export const tool = defineTool('json', {
|
||||
path: 'validate-json',
|
||||
path: 'validateJson',
|
||||
icon: 'material-symbols:check-circle',
|
||||
|
||||
keywords: ['json', 'validate', 'check', 'syntax', 'errors'],
|
||||
|
@@ -11,6 +11,7 @@ export const tool = defineTool('list', {
|
||||
i18n: {
|
||||
name: 'list:duplicate.title',
|
||||
description: 'list:duplicate.description',
|
||||
shortDescription: 'list:duplicate.shortDescription'
|
||||
shortDescription: 'list:duplicate.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -11,6 +11,7 @@ export const tool = defineTool('list', {
|
||||
i18n: {
|
||||
name: 'list:findMostPopular.title',
|
||||
description: 'list:findMostPopular.description',
|
||||
shortDescription: 'list:findMostPopular.shortDescription'
|
||||
shortDescription: 'list:findMostPopular.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('list', {
|
||||
i18n: {
|
||||
name: 'list:findUnique.title',
|
||||
description: 'list:findUnique.description',
|
||||
shortDescription: 'list:findUnique.shortDescription'
|
||||
shortDescription: 'list:findUnique.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('list', {
|
||||
i18n: {
|
||||
name: 'list:group.title',
|
||||
description: 'list:group.description',
|
||||
shortDescription: 'list:group.shortDescription'
|
||||
shortDescription: 'list:group.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -5,12 +5,12 @@ import { lazy } from 'react';
|
||||
export const tool = defineTool('list', {
|
||||
path: 'reverse',
|
||||
icon: 'proicons:reverse',
|
||||
|
||||
keywords: ['reverse'],
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'list:reverse.title',
|
||||
description: 'list:reverse.description',
|
||||
shortDescription: 'list:reverse.shortDescription'
|
||||
}
|
||||
shortDescription: 'list:reverse.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
},
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
||||
|
@@ -11,6 +11,7 @@ export const tool = defineTool('list', {
|
||||
i18n: {
|
||||
name: 'list:rotate.title',
|
||||
description: 'list:rotate.description',
|
||||
shortDescription: 'list:rotate.shortDescription'
|
||||
shortDescription: 'list:rotate.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -11,6 +11,7 @@ export const tool = defineTool('list', {
|
||||
i18n: {
|
||||
name: 'list:shuffle.title',
|
||||
description: 'list:shuffle.description',
|
||||
shortDescription: 'list:shuffle.shortDescription'
|
||||
shortDescription: 'list:shuffle.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -11,6 +11,7 @@ export const tool = defineTool('list', {
|
||||
i18n: {
|
||||
name: 'list:sort.title',
|
||||
description: 'list:sort.description',
|
||||
shortDescription: 'list:sort.shortDescription'
|
||||
shortDescription: 'list:sort.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('list', {
|
||||
i18n: {
|
||||
name: 'list:truncate.title',
|
||||
description: 'list:truncate.description',
|
||||
shortDescription: 'list:truncate.shortDescription'
|
||||
shortDescription: 'list:truncate.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -11,6 +11,7 @@ export const tool = defineTool('list', {
|
||||
i18n: {
|
||||
name: 'list:unwrap.title',
|
||||
description: 'list:unwrap.description',
|
||||
shortDescription: 'list:unwrap.shortDescription'
|
||||
shortDescription: 'list:unwrap.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -11,6 +11,7 @@ export const tool = defineTool('list', {
|
||||
i18n: {
|
||||
name: 'list:wrap.title',
|
||||
description: 'list:wrap.description',
|
||||
shortDescription: 'list:wrap.shortDescription'
|
||||
shortDescription: 'list:wrap.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('number', {
|
||||
i18n: {
|
||||
name: 'number:arithmeticSequence.title',
|
||||
description: 'number:arithmeticSequence.description',
|
||||
shortDescription: 'number:arithmeticSequence.shortDescription'
|
||||
shortDescription: 'number:arithmeticSequence.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -11,6 +11,7 @@ export const tool = defineTool('number', {
|
||||
i18n: {
|
||||
name: 'number:sum.title',
|
||||
description: 'number:sum.description',
|
||||
shortDescription: 'number:sum.shortDescription'
|
||||
shortDescription: 'number:sum.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -19,11 +19,11 @@ export const tool = defineTool('pdf', {
|
||||
'browser',
|
||||
'webassembly'
|
||||
],
|
||||
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'pdf:compressPdf.title',
|
||||
description: 'pdf:compressPdf.description',
|
||||
shortDescription: 'pdf:compressPdf.shortDescription'
|
||||
shortDescription: 'pdf:compressPdf.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -5,7 +5,8 @@ export const tool = defineTool('pdf', {
|
||||
i18n: {
|
||||
name: 'pdf:editor.title',
|
||||
description: 'pdf:editor.description',
|
||||
shortDescription: 'pdf:editor.shortDescription'
|
||||
shortDescription: 'pdf:editor.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
},
|
||||
|
||||
path: 'editor',
|
||||
|
@@ -9,6 +9,7 @@ export const meta = defineTool('pdf', {
|
||||
i18n: {
|
||||
name: 'pdf:mergePdf.title',
|
||||
description: 'pdf:mergePdf.description',
|
||||
shortDescription: 'pdf:mergePdf.shortDescription'
|
||||
shortDescription: 'pdf:mergePdf.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -9,6 +9,7 @@ export const meta = defineTool('pdf', {
|
||||
i18n: {
|
||||
name: 'pdf:pdfToEpub.title',
|
||||
description: 'pdf:pdfToEpub.description',
|
||||
shortDescription: 'pdf:pdfToEpub.shortDescription'
|
||||
shortDescription: 'pdf:pdfToEpub.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -6,13 +6,13 @@ export const tool = defineTool('pdf', {
|
||||
name: 'pdf:pdfToPng.title',
|
||||
description: 'pdf:pdfToPng.description',
|
||||
shortDescription: 'pdf:pdfToPng.shortDescription',
|
||||
longDescription: 'pdf:pdfToPng.longDescription'
|
||||
longDescription: 'pdf:pdfToPng.longDescription',
|
||||
userTypes: ['generalUsers']
|
||||
},
|
||||
|
||||
path: 'pdf-to-png',
|
||||
icon: 'mdi:image-multiple', // Iconify icon ID
|
||||
|
||||
keywords: ['pdf', 'png', 'convert', 'image', 'extract', 'pages'],
|
||||
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
||||
|
@@ -18,11 +18,11 @@ export const tool = defineTool('pdf', {
|
||||
'browser',
|
||||
'encryption'
|
||||
],
|
||||
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'pdf:protectPdf.title',
|
||||
description: 'pdf:protectPdf.description',
|
||||
shortDescription: 'pdf:protectPdf.shortDescription'
|
||||
shortDescription: 'pdf:protectPdf.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -6,13 +6,13 @@ export const tool = defineTool('pdf', {
|
||||
name: 'pdf:rotatePdf.title',
|
||||
description: 'pdf:rotatePdf.description',
|
||||
shortDescription: 'pdf:rotatePdf.shortDescription',
|
||||
longDescription: 'pdf:rotatePdf.longDescription'
|
||||
longDescription: 'pdf:rotatePdf.longDescription',
|
||||
userTypes: ['generalUsers']
|
||||
},
|
||||
|
||||
path: 'rotate-pdf',
|
||||
icon: 'carbon:rotate',
|
||||
|
||||
keywords: ['pdf', 'rotate', 'rotation', 'document', 'pages', 'orientation'],
|
||||
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
||||
|
@@ -9,6 +9,7 @@ export const meta = defineTool('pdf', {
|
||||
i18n: {
|
||||
name: 'pdf:splitPdf.title',
|
||||
description: 'pdf:splitPdf.description',
|
||||
shortDescription: 'pdf:splitPdf.shortDescription'
|
||||
shortDescription: 'pdf:splitPdf.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('string', {
|
||||
i18n: {
|
||||
name: 'string:base64.title',
|
||||
description: 'string:base64.description',
|
||||
shortDescription: 'string:base64.shortDescription'
|
||||
shortDescription: 'string:base64.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -11,6 +11,7 @@ export const tool = defineTool('string', {
|
||||
i18n: {
|
||||
name: 'string:censor.title',
|
||||
description: 'string:censor.description',
|
||||
shortDescription: 'string:censor.shortDescription'
|
||||
shortDescription: 'string:censor.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -11,6 +11,7 @@ export const tool = defineTool('string', {
|
||||
i18n: {
|
||||
name: 'string:createPalindrome.title',
|
||||
description: 'string:createPalindrome.description',
|
||||
shortDescription: 'string:createPalindrome.shortDescription'
|
||||
shortDescription: 'string:createPalindrome.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -11,6 +11,7 @@ export const tool = defineTool('string', {
|
||||
i18n: {
|
||||
name: 'string:extractSubstring.title',
|
||||
description: 'string:extractSubstring.description',
|
||||
shortDescription: 'string:extractSubstring.shortDescription'
|
||||
shortDescription: 'string:extractSubstring.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -3,13 +3,15 @@ import { lazy } from 'react';
|
||||
|
||||
export const tool = defineTool('string', {
|
||||
path: 'join',
|
||||
|
||||
icon: 'material-symbols-light:join',
|
||||
|
||||
keywords: ['join'],
|
||||
keywords: ['text', 'join'],
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'string:join.title',
|
||||
description: 'string:join.description',
|
||||
shortDescription: 'string:join.shortDescription'
|
||||
shortDescription: 'string:join.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -11,6 +11,7 @@ export const tool = defineTool('string', {
|
||||
i18n: {
|
||||
name: 'string:palindrome.title',
|
||||
description: 'string:palindrome.description',
|
||||
shortDescription: 'string:palindrome.shortDescription'
|
||||
shortDescription: 'string:palindrome.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -11,6 +11,7 @@ export const tool = defineTool('string', {
|
||||
i18n: {
|
||||
name: 'string:quote.title',
|
||||
description: 'string:quote.description',
|
||||
shortDescription: 'string:quote.shortDescription'
|
||||
shortDescription: 'string:quote.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -11,6 +11,7 @@ export const tool = defineTool('string', {
|
||||
i18n: {
|
||||
name: 'string:randomizeCase.title',
|
||||
description: 'string:randomizeCase.description',
|
||||
shortDescription: 'string:randomizeCase.shortDescription'
|
||||
shortDescription: 'string:randomizeCase.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('string', {
|
||||
i18n: {
|
||||
name: 'string:removeDuplicateLines.title',
|
||||
description: 'string:removeDuplicateLines.description',
|
||||
shortDescription: 'string:removeDuplicateLines.shortDescription'
|
||||
shortDescription: 'string:removeDuplicateLines.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -11,6 +11,7 @@ export const tool = defineTool('string', {
|
||||
i18n: {
|
||||
name: 'string:repeat.title',
|
||||
description: 'string:repeat.description',
|
||||
shortDescription: 'string:repeat.shortDescription'
|
||||
shortDescription: 'string:repeat.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('string', {
|
||||
i18n: {
|
||||
name: 'string:reverse.title',
|
||||
description: 'string:reverse.description',
|
||||
shortDescription: 'string:reverse.shortDescription'
|
||||
shortDescription: 'string:reverse.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -6,7 +6,8 @@ export const tool = defineTool('string', {
|
||||
i18n: {
|
||||
name: 'string:rot13.title',
|
||||
description: 'string:rot13.description',
|
||||
shortDescription: 'string:rot13.shortDescription'
|
||||
shortDescription: 'string:rot13.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
},
|
||||
|
||||
path: 'rot13',
|
||||
|
@@ -6,7 +6,8 @@ export const tool = defineTool('string', {
|
||||
i18n: {
|
||||
name: 'string:rotate.title',
|
||||
description: 'string:rotate.description',
|
||||
shortDescription: 'string:rotate.shortDescription'
|
||||
shortDescription: 'string:rotate.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
},
|
||||
|
||||
path: 'rotate',
|
||||
|
@@ -3,6 +3,7 @@ import { lazy } from 'react';
|
||||
|
||||
export const tool = defineTool('string', {
|
||||
path: 'split',
|
||||
|
||||
icon: 'material-symbols-light:call-split',
|
||||
|
||||
keywords: ['split'],
|
||||
@@ -10,6 +11,7 @@ export const tool = defineTool('string', {
|
||||
i18n: {
|
||||
name: 'string:split.title',
|
||||
description: 'string:split.description',
|
||||
shortDescription: 'string:split.shortDescription'
|
||||
shortDescription: 'string:split.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -11,6 +11,7 @@ export const tool = defineTool('string', {
|
||||
i18n: {
|
||||
name: 'string:statistic.title',
|
||||
description: 'string:statistic.description',
|
||||
shortDescription: 'string:statistic.shortDescription'
|
||||
shortDescription: 'string:statistic.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -5,7 +5,8 @@ export const tool = defineTool('string', {
|
||||
i18n: {
|
||||
name: 'string:textReplacer.title',
|
||||
description: 'string:textReplacer.description',
|
||||
shortDescription: 'string:textReplacer.shortDescription'
|
||||
shortDescription: 'string:textReplacer.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
},
|
||||
|
||||
path: 'replacer',
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('string', {
|
||||
i18n: {
|
||||
name: 'string:toMorse.title',
|
||||
description: 'string:toMorse.description',
|
||||
shortDescription: 'string:toMorse.shortDescription'
|
||||
shortDescription: 'string:toMorse.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('string', {
|
||||
i18n: {
|
||||
name: 'string:truncate.title',
|
||||
description: 'string:truncate.description',
|
||||
shortDescription: 'string:truncate.shortDescription'
|
||||
shortDescription: 'string:truncate.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('string', {
|
||||
i18n: {
|
||||
name: 'string:uppercase.title',
|
||||
description: 'string:uppercase.description',
|
||||
shortDescription: 'string:uppercase.shortDescription'
|
||||
shortDescription: 'string:uppercase.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('time', {
|
||||
i18n: {
|
||||
name: 'time:checkLeapYears.title',
|
||||
description: 'time:checkLeapYears.description',
|
||||
shortDescription: 'time:checkLeapYears.shortDescription'
|
||||
shortDescription: 'time:checkLeapYears.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('time', {
|
||||
i18n: {
|
||||
name: 'time:convertDaysToHours.title',
|
||||
description: 'time:convertDaysToHours.description',
|
||||
shortDescription: 'time:convertDaysToHours.shortDescription'
|
||||
shortDescription: 'time:convertDaysToHours.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('time', {
|
||||
i18n: {
|
||||
name: 'time:convertHoursToDays.title',
|
||||
description: 'time:convertHoursToDays.description',
|
||||
shortDescription: 'time:convertHoursToDays.shortDescription'
|
||||
shortDescription: 'time:convertHoursToDays.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('time', {
|
||||
i18n: {
|
||||
name: 'time:convertSecondsToTime.title',
|
||||
description: 'time:convertSecondsToTime.description',
|
||||
shortDescription: 'time:convertSecondsToTime.shortDescription'
|
||||
shortDescription: 'time:convertSecondsToTime.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -5,11 +5,12 @@ export const tool = defineTool('time', {
|
||||
path: 'convert-time-to-seconds',
|
||||
icon: 'material-symbols:schedule',
|
||||
|
||||
keywords: ['time', 'seconds', 'convert', 'format'],
|
||||
keywords: ['time', 'seconds', 'convert', 'format', 'HH:MM:SS'],
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'time:convertTimeToSeconds.title',
|
||||
description: 'time:convertTimeToSeconds.description',
|
||||
shortDescription: 'time:convertTimeToSeconds.shortDescription'
|
||||
shortDescription: 'time:convertTimeToSeconds.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -4,12 +4,21 @@ import { lazy } from 'react';
|
||||
export const tool = defineTool('time', {
|
||||
path: 'crontab-guru',
|
||||
icon: 'material-symbols:schedule',
|
||||
|
||||
keywords: ['cron', 'schedule', 'automation', 'expression'],
|
||||
keywords: [
|
||||
'crontab',
|
||||
'cron',
|
||||
'schedule',
|
||||
'guru',
|
||||
'time',
|
||||
'expression',
|
||||
'parser',
|
||||
'explain'
|
||||
],
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'time:crontabGuru.title',
|
||||
description: 'time:crontabGuru.description',
|
||||
shortDescription: 'time:crontabGuru.shortDescription'
|
||||
shortDescription: 'time:crontabGuru.shortDescription',
|
||||
userTypes: ['developers']
|
||||
}
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('time', {
|
||||
i18n: {
|
||||
name: 'time:timeBetweenDates.title',
|
||||
description: 'time:timeBetweenDates.description',
|
||||
shortDescription: 'time:timeBetweenDates.shortDescription'
|
||||
shortDescription: 'time:timeBetweenDates.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('time', {
|
||||
i18n: {
|
||||
name: 'time:truncateClockTime.title',
|
||||
description: 'time:truncateClockTime.description',
|
||||
shortDescription: 'time:truncateClockTime.shortDescription'
|
||||
shortDescription: 'time:truncateClockTime.shortDescription',
|
||||
userTypes: ['generalUsers', 'developers']
|
||||
}
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('video', {
|
||||
i18n: {
|
||||
name: 'video:changeSpeed.title',
|
||||
description: 'video:changeSpeed.description',
|
||||
shortDescription: 'video:changeSpeed.shortDescription'
|
||||
shortDescription: 'video:changeSpeed.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -8,15 +8,20 @@ export const tool = defineTool('video', {
|
||||
keywords: [
|
||||
'compress',
|
||||
'video',
|
||||
'resize',
|
||||
'scale',
|
||||
'resolution',
|
||||
'reduce size'
|
||||
'reduce',
|
||||
'size',
|
||||
'optimize',
|
||||
'mp4',
|
||||
'mov',
|
||||
'avi',
|
||||
'video editing',
|
||||
'shrink'
|
||||
],
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'video:compress.title',
|
||||
description: 'video:compress.description',
|
||||
shortDescription: 'video:compress.shortDescription'
|
||||
shortDescription: 'video:compress.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -4,12 +4,22 @@ import { lazy } from 'react';
|
||||
export const tool = defineTool('video', {
|
||||
path: 'crop-video',
|
||||
icon: 'material-symbols:crop',
|
||||
|
||||
keywords: ['video', 'crop', 'trim', 'edit', 'resize'],
|
||||
component: lazy(() => import('./index')),
|
||||
keywords: [
|
||||
'crop',
|
||||
'video',
|
||||
'trim',
|
||||
'aspect ratio',
|
||||
'mp4',
|
||||
'mov',
|
||||
'avi',
|
||||
'video editing',
|
||||
'resize'
|
||||
],
|
||||
i18n: {
|
||||
name: 'video:cropVideo.title',
|
||||
description: 'video:cropVideo.description',
|
||||
shortDescription: 'video:cropVideo.shortDescription'
|
||||
}
|
||||
shortDescription: 'video:cropVideo.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
},
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('video', {
|
||||
i18n: {
|
||||
name: 'video:flip.title',
|
||||
description: 'video:flip.description',
|
||||
shortDescription: 'video:flip.shortDescription'
|
||||
shortDescription: 'video:flip.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('video', {
|
||||
i18n: {
|
||||
name: 'video:loop.title',
|
||||
description: 'video:loop.description',
|
||||
shortDescription: 'video:loop.shortDescription'
|
||||
shortDescription: 'video:loop.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -10,6 +10,7 @@ export const tool = defineTool('video', {
|
||||
i18n: {
|
||||
name: 'video:rotate.title',
|
||||
description: 'video:rotate.description',
|
||||
shortDescription: 'video:rotate.shortDescription'
|
||||
shortDescription: 'video:rotate.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -9,6 +9,7 @@ export const tool = defineTool('video', {
|
||||
i18n: {
|
||||
name: 'video:trim.title',
|
||||
description: 'video:trim.description',
|
||||
shortDescription: 'video:trim.shortDescription'
|
||||
shortDescription: 'video:trim.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
@@ -9,6 +9,7 @@ export const tool = defineTool('video', {
|
||||
i18n: {
|
||||
name: 'video:videoToGif.title',
|
||||
description: 'video:videoToGif.description',
|
||||
shortDescription: 'video:videoToGif.shortDescription'
|
||||
shortDescription: 'video:videoToGif.shortDescription',
|
||||
userTypes: ['generalUsers']
|
||||
}
|
||||
});
|
||||
|
77
src/providers/UserTypeFilterProvider.tsx
Normal file
77
src/providers/UserTypeFilterProvider.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useState,
|
||||
useEffect,
|
||||
ReactNode,
|
||||
useMemo
|
||||
} from 'react';
|
||||
import { UserType } from '@tools/defineTool';
|
||||
|
||||
interface UserTypeFilterContextType {
|
||||
selectedUserTypes: UserType[];
|
||||
setSelectedUserTypes: (userTypes: UserType[]) => void;
|
||||
}
|
||||
|
||||
const UserTypeFilterContext = createContext<UserTypeFilterContextType | null>(
|
||||
null
|
||||
);
|
||||
|
||||
interface UserTypeFilterProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function UserTypeFilterProvider({
|
||||
children
|
||||
}: UserTypeFilterProviderProps) {
|
||||
const [selectedUserTypes, setSelectedUserTypes] = useState<UserType[]>(() => {
|
||||
try {
|
||||
const saved = localStorage.getItem('selectedUserTypes');
|
||||
return saved ? JSON.parse(saved) : [];
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'Error loading selectedUserTypes from localStorage:',
|
||||
error
|
||||
);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
localStorage.setItem(
|
||||
'selectedUserTypes',
|
||||
JSON.stringify(selectedUserTypes)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error saving selectedUserTypes to localStorage:', error);
|
||||
}
|
||||
}, [selectedUserTypes]);
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
selectedUserTypes,
|
||||
setSelectedUserTypes
|
||||
}),
|
||||
[selectedUserTypes]
|
||||
);
|
||||
|
||||
return (
|
||||
<UserTypeFilterContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</UserTypeFilterContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useUserTypeFilter(): UserTypeFilterContextType {
|
||||
const context = useContext(UserTypeFilterContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useUserTypeFilter must be used within a UserTypeFilterProvider. ' +
|
||||
'Make sure your component is wrapped with <UserTypeFilterProvider>.'
|
||||
);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
@@ -4,6 +4,8 @@ import { IconifyIcon } from '@iconify/react';
|
||||
import { FullI18nKey, validNamespaces } from '../i18n';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export type UserType = 'generalUsers' | 'developers';
|
||||
|
||||
export interface ToolMeta {
|
||||
path: string;
|
||||
component: LazyExoticComponent<JSXElementConstructor<ToolComponentProps>>;
|
||||
@@ -14,21 +16,22 @@ export interface ToolMeta {
|
||||
description: FullI18nKey;
|
||||
shortDescription: FullI18nKey;
|
||||
longDescription?: FullI18nKey;
|
||||
userTypes?: UserType[];
|
||||
};
|
||||
}
|
||||
|
||||
export type ToolCategory =
|
||||
| 'string'
|
||||
| 'image-generic'
|
||||
| 'png'
|
||||
| 'number'
|
||||
| 'gif'
|
||||
| 'video'
|
||||
| 'list'
|
||||
| 'json'
|
||||
| 'time'
|
||||
| 'csv'
|
||||
| 'video'
|
||||
| 'pdf'
|
||||
| 'image-generic'
|
||||
| 'audio'
|
||||
| 'xml';
|
||||
|
||||
@@ -41,6 +44,7 @@ export interface DefinedTool {
|
||||
icon: IconifyIcon | string;
|
||||
keywords: string[];
|
||||
component: () => JSX.Element;
|
||||
userTypes?: UserType[];
|
||||
}
|
||||
|
||||
export interface ToolComponentProps {
|
||||
@@ -62,6 +66,7 @@ export const defineTool = (
|
||||
description: i18n.description,
|
||||
shortDescription: i18n.shortDescription,
|
||||
keywords,
|
||||
userTypes: i18n.userTypes,
|
||||
component: function ToolComponent() {
|
||||
const { t } = useTranslation(validNamespaces);
|
||||
return (
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { stringTools } from '../pages/tools/string';
|
||||
import { imageTools } from '../pages/tools/image';
|
||||
import { DefinedTool, ToolCategory } from './defineTool';
|
||||
import { DefinedTool, ToolCategory, UserType } from './defineTool';
|
||||
import { capitalizeFirstLetter } from '@utils/string';
|
||||
import { numberTools } from '../pages/tools/number';
|
||||
import { videoTools } from '../pages/tools/video';
|
||||
@@ -136,6 +136,36 @@ const categoriesConfig: {
|
||||
title: 'translation:categories.xml.title'
|
||||
}
|
||||
];
|
||||
const CATEGORIES_USER_TYPES_MAPPINGS: Partial<Record<ToolCategory, UserType>> =
|
||||
{
|
||||
xml: 'developers',
|
||||
csv: 'developers',
|
||||
json: 'developers',
|
||||
gif: 'generalUsers',
|
||||
png: 'generalUsers',
|
||||
'image-generic': 'generalUsers',
|
||||
video: 'generalUsers',
|
||||
audio: 'generalUsers'
|
||||
};
|
||||
// Filter tools by user types
|
||||
export const filterToolsByUserTypes = (
|
||||
tools: DefinedTool[],
|
||||
userTypes: UserType[]
|
||||
): DefinedTool[] => {
|
||||
if (userTypes.length === 0) return tools;
|
||||
|
||||
return tools.filter((tool) => {
|
||||
if (CATEGORIES_USER_TYPES_MAPPINGS[tool.type]) {
|
||||
return userTypes.includes(CATEGORIES_USER_TYPES_MAPPINGS[tool.type]!);
|
||||
}
|
||||
// If tool has no userTypes defined, show it to all users
|
||||
if (!tool.userTypes || tool.userTypes.length === 0) return true;
|
||||
|
||||
// Check if tool has any of the selected user types
|
||||
return tool.userTypes.some((userType) => userTypes.includes(userType));
|
||||
});
|
||||
};
|
||||
|
||||
// use for changelogs
|
||||
// console.log(
|
||||
// 'tools',
|
||||
@@ -144,12 +174,22 @@ const categoriesConfig: {
|
||||
export const filterTools = (
|
||||
tools: DefinedTool[],
|
||||
query: string,
|
||||
userTypes: UserType[] = [],
|
||||
t: TFunction<I18nNamespaces[]>
|
||||
): DefinedTool[] => {
|
||||
if (!query) return tools;
|
||||
let filteredTools = tools;
|
||||
|
||||
// First filter by user types
|
||||
if (userTypes.length > 0) {
|
||||
filteredTools = filterToolsByUserTypes(tools, userTypes);
|
||||
}
|
||||
|
||||
// Then filter by search query
|
||||
if (!query) return filteredTools;
|
||||
|
||||
const lowerCaseQuery = query.toLowerCase();
|
||||
return tools.filter(
|
||||
|
||||
return filteredTools.filter(
|
||||
(tool) =>
|
||||
t(tool.name).toLowerCase().includes(lowerCaseQuery) ||
|
||||
t(tool.description).toLowerCase().includes(lowerCaseQuery) ||
|
||||
@@ -161,6 +201,7 @@ export const filterTools = (
|
||||
};
|
||||
|
||||
export const getToolsByCategory = (
|
||||
userTypes: UserType[] = [],
|
||||
t: TFunction<I18nNamespaces[]>
|
||||
): {
|
||||
title: string;
|
||||
@@ -170,14 +211,28 @@ export const getToolsByCategory = (
|
||||
type: ToolCategory;
|
||||
example: { title: string; path: string };
|
||||
tools: DefinedTool[];
|
||||
userTypes: UserType[]; // <-- Add this line
|
||||
}[] => {
|
||||
const groupedByType: Partial<Record<ToolCategory, DefinedTool[]>> =
|
||||
Object.groupBy(tools, ({ type }) => type);
|
||||
|
||||
return (Object.entries(groupedByType) as Entries<typeof groupedByType>)
|
||||
.map(([type, tools]) => {
|
||||
const categoryConfig = categoriesConfig.find(
|
||||
(config) => config.type === type
|
||||
);
|
||||
|
||||
// Filter tools by user types if specified
|
||||
const filteredTools =
|
||||
userTypes.length > 0
|
||||
? filterToolsByUserTypes(tools ?? [], userTypes)
|
||||
: tools ?? [];
|
||||
|
||||
// Aggregate unique userTypes from all tools in this category
|
||||
const aggregatedUserTypes = Array.from(
|
||||
new Set((filteredTools ?? []).flatMap((tool) => tool.userTypes ?? []))
|
||||
);
|
||||
|
||||
return {
|
||||
rawTitle: categoryConfig?.title
|
||||
? t(categoryConfig.title)
|
||||
@@ -188,12 +243,22 @@ export const getToolsByCategory = (
|
||||
description: categoryConfig?.value ? t(categoryConfig.value) : '',
|
||||
type,
|
||||
icon: categoryConfig!.icon,
|
||||
tools: tools ?? [],
|
||||
example: tools
|
||||
? { title: tools[0].name, path: tools[0].path }
|
||||
: { title: '', path: '' }
|
||||
tools: filteredTools,
|
||||
example:
|
||||
filteredTools.length > 0
|
||||
? { title: filteredTools[0].name, path: filteredTools[0].path }
|
||||
: { title: '', path: '' },
|
||||
userTypes: aggregatedUserTypes // <-- Add this line
|
||||
};
|
||||
})
|
||||
.filter((category) => category.tools.length > 0)
|
||||
.filter((category) =>
|
||||
userTypes.length > 0
|
||||
? [...category.userTypes, CATEGORIES_USER_TYPES_MAPPINGS[category.type]]
|
||||
.filter(Boolean)
|
||||
.some((categoryUserType) => userTypes.includes(categoryUserType!))
|
||||
: true
|
||||
) // Only show categories with tools
|
||||
.sort(
|
||||
(a, b) =>
|
||||
toolCategoriesOrder.indexOf(a.type) -
|
||||
|
@@ -114,7 +114,7 @@ export const getToolCategoryTitle = (
|
||||
categoryName: string,
|
||||
t: TFunction<I18nNamespaces[]>
|
||||
): string =>
|
||||
getToolsByCategory(t).find((category) => category.type === categoryName)!
|
||||
getToolsByCategory([], t).find((category) => category.type === categoryName)!
|
||||
.rawTitle;
|
||||
|
||||
// Type guard to check if a value is a valid I18nNamespaces
|
||||
|
Reference in New Issue
Block a user