fix: misc

This commit is contained in:
Ibrahima G. Coulibaly
2025-02-23 14:11:21 +00:00
parent 70ad843b72
commit 97b940a803
13 changed files with 278 additions and 147 deletions

44
.idea/workspace.xml generated
View File

@@ -4,10 +4,20 @@
<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: ctrl v"> <list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="feat: update readme">
<change afterPath="$PROJECT_DIR$/src/assets/logo.png" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/pages/home/Categories.tsx" afterDir="false" />
<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$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" /> <change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" 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$/scripts/create-tool.mjs" beforeDir="false" afterPath="$PROJECT_DIR$/scripts/create-tool.mjs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/Hero.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/Hero.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/Navbar/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/Navbar/index.tsx" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/components/Navbar/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/Navbar/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/home/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/home/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/list/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/index.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/index.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/tools/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/tools/index.ts" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -78,7 +88,7 @@
&quot;git-widget-placeholder&quot;: &quot;main&quot;, &quot;git-widget-placeholder&quot;: &quot;main&quot;,
&quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;, &quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;, &quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;C:/Users/HP/IdeaProjects/omni-tools/src/components/options&quot;, &quot;last_opened_file_path&quot;: &quot;C:/Users/Ibrahima/IdeaProjects/omni-tools/src/assets&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;, &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
@@ -111,11 +121,11 @@
</component> </component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS"> <key name="CopyFile.RECENT_KEYS">
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\assets" />
<recent name="C:\Users\HP\IdeaProjects\omni-tools\src\components\options" /> <recent name="C:\Users\HP\IdeaProjects\omni-tools\src\components\options" />
<recent name="C:\Users\HP\IdeaProjects\omni-tools\src\assets" /> <recent name="C:\Users\HP\IdeaProjects\omni-tools\src\assets" />
<recent name="C:\Users\HP\IdeaProjects\omni-tools\src\pages\string" /> <recent name="C:\Users\HP\IdeaProjects\omni-tools\src\pages\string" />
<recent name="C:\Users\HP\IdeaProjects\omni-tools\src\pages\string\split" /> <recent name="C:\Users\HP\IdeaProjects\omni-tools\src\pages\string\split" />
<recent name="C:\Users\HP\IdeaProjects\omni-tools\src\pages\images" />
</key> </key>
<key name="MoveFile.RECENT_KEYS"> <key name="MoveFile.RECENT_KEYS">
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\tools" /> <recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\tools" />
@@ -250,15 +260,11 @@
<workItem from="1720912096050" duration="3065000" /> <workItem from="1720912096050" duration="3065000" />
<workItem from="1740259920741" duration="7742000" /> <workItem from="1740259920741" duration="7742000" />
<workItem from="1740270391152" duration="690000" /> <workItem from="1740270391152" duration="690000" />
<workItem from="1740274898695" duration="1168000" /> <workItem from="1740274898695" duration="2231000" />
</task> <workItem from="1740295530385" duration="1120000" />
<task id="LOCAL-00054" summary="chore: sum tests"> <workItem from="1740300354462" duration="1059000" />
<option name="closed" value="true" /> <workItem from="1740301493702" duration="8924000" />
<created>1719282131977</created> <workItem from="1740318886545" duration="856000" />
<option name="number" value="00054" />
<option name="presentableId" value="LOCAL-00054" />
<option name="project" value="LOCAL" />
<updated>1719282131977</updated>
</task> </task>
<task id="LOCAL-00055" summary="fix: readme"> <task id="LOCAL-00055" summary="fix: readme">
<option name="closed" value="true" /> <option name="closed" value="true" />
@@ -644,7 +650,15 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1740267666455</updated> <updated>1740267666455</updated>
</task> </task>
<option name="localTasksCounter" value="103" /> <task id="LOCAL-00103" summary="feat: update readme">
<option name="closed" value="true" />
<created>1740276092528</created>
<option name="number" value="00103" />
<option name="presentableId" value="LOCAL-00103" />
<option name="project" value="LOCAL" />
<updated>1740276092528</updated>
</task>
<option name="localTasksCounter" value="104" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@@ -676,7 +690,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: remove unused deps" />
<MESSAGE value="style: lint" /> <MESSAGE value="style: lint" />
<MESSAGE value="fix: radio and list sort init" /> <MESSAGE value="fix: radio and list sort init" />
<MESSAGE value="chore: formik updateField" /> <MESSAGE value="chore: formik updateField" />
@@ -701,7 +714,8 @@
<MESSAGE value="refactor: optimize imports" /> <MESSAGE value="refactor: optimize imports" />
<MESSAGE value="chore: use string tools" /> <MESSAGE value="chore: use string tools" />
<MESSAGE value="fix: ctrl v" /> <MESSAGE value="fix: ctrl v" />
<option name="LAST_COMMIT_MESSAGE" value="fix: ctrl v" /> <MESSAGE value="feat: update readme" />
<option name="LAST_COMMIT_MESSAGE" value="feat: update readme" />
</component> </component>
<component name="XSLT-Support.FileAssociations.UIState"> <component name="XSLT-Support.FileAssociations.UIState">
<expand /> <expand />

View File

@@ -1,9 +1,13 @@
# OmniTools <p align="center"><img src="src/assets/logo.png" width="80"></p>
<h1 align="center">OmniTools</h1>
Welcome to **OmniTools**, a self-hosted alternative to PineTools.com. [//]: # ([![Docker Pulls]&#40;https://img.shields.io/docker/pulls/iib0011/omni-tools&#41;]&#40;https://hub.docker.com/r/iib0011/omni-tools&#41;)
This project offers a variety of online tools to help with everyday tasks, [![Discord](https://img.shields.io/discord/1342971141823664179?label=Discord)](https://discord.gg/SDbbn3hT4b)
all available for free and open for community contributions. Whether you are manipulating images, crunching numbers, or coding, OmniTools has you covered. Please don't forget to star the
Welcome to OmniTools, a self-hosted platform offering a variety of online tools to simplify everyday tasks.
Whether you are manipulating images, crunching numbers, or
coding, OmniTools has you covered. Please don't forget to star the
repo to support us. repo to support us.
Here is the [demo](https://omnitools.netlify.app/) website. Here is the [demo](https://omnitools.netlify.app/) website.
@@ -19,27 +23,43 @@ Here is the [demo](https://omnitools.netlify.app/) website.
## Features ## Features
OmniTools includes a variety of tools, such as: We strive to offer a variety of tools, including:
1. **Image/Video/Binary tools** ## **Image/Video/Binary Tools**
- Image Resizer, Image converter, Video trimmer, video reverser, etc. - Image Resizer
- Image Converter
- Video Trimmer
- Video Reverser
- And more...
2. **Math tools** ## **String/List Tools**
- Generate prime numbers, generate perfect numbers etc. - Case Converters
- List Shuffler
- Text Formatters
- And more...
3. **String/List Tools** ## **Date and Time Tools**
- Case converters, shuffle list, text formatters, etc. - Date Calculators
- Time Zone Converters
- And more...
4. **Date and Time Tools** ## **Math Tools**
- Date calculators, time zone converters, etc. - Generate Prime Numbers
- Generate Perfect Numbers
- And more...
5. **Miscellaneous Tools** ## **Miscellaneous Tools**
- JSON, XML tools, CSV tools etc. - JSON Tools
- XML Tools
- CSV Tools
- And more...
Stay tuned as we continue to expand and improve our collection!
## Self-host/Run ## Self-host/Run
@@ -49,6 +69,8 @@ docker run -d --name omni-tools --restart unless-stopped -p 8080:80 iib0011/omni
## Contribute ## Contribute
This is a React Project with Typescript Material UI.
### Project setup ### Project setup
```bash ```bash

16
package-lock.json generated
View File

@@ -10,6 +10,8 @@
"dependencies": { "dependencies": {
"@emotion/react": "^11.11.4", "@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5", "@emotion/styled": "^11.11.5",
"@hugeicons/core-free-icons": "^1.0.10",
"@hugeicons/react": "^1.0.3",
"@mui/icons-material": "^5.15.20", "@mui/icons-material": "^5.15.20",
"@mui/material": "^5.15.20", "@mui/material": "^5.15.20",
"@playwright/test": "^1.45.0", "@playwright/test": "^1.45.0",
@@ -1398,6 +1400,20 @@
"@hapi/hoek": "^9.0.0" "@hapi/hoek": "^9.0.0"
} }
}, },
"node_modules/@hugeicons/core-free-icons": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-1.0.10.tgz",
"integrity": "sha512-XMjwTffefQGJ0B3gjnS9IV2UqM5qYT4WUJjD+cD7x6TfwE8rSAb+foGNbcyCjpXKVOnuyaJa+y4ukrPyNY/DBw==",
"license": "MIT"
},
"node_modules/@hugeicons/react": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@hugeicons/react/-/react-1.0.3.tgz",
"integrity": "sha512-NJN8PmxTZlkt3T9a7uNZLhkJlIyQUt+sMxM5Qa/UH1qC1fBkwI7C7HSY/y4f7jjo5SQl7zRkm3hWH9tpWuHmWw==",
"peerDependencies": {
"react": ">=16.0.0"
}
},
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.11.14", "version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",

View File

@@ -27,6 +27,8 @@
"dependencies": { "dependencies": {
"@emotion/react": "^11.11.4", "@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5", "@emotion/styled": "^11.11.5",
"@hugeicons/core-free-icons": "^1.0.10",
"@hugeicons/react": "^1.0.3",
"@mui/icons-material": "^5.15.20", "@mui/icons-material": "^5.15.20",
"@mui/material": "^5.15.20", "@mui/material": "^5.15.20",
"@playwright/test": "^1.45.0", "@playwright/test": "^1.45.0",

View File

@@ -1,59 +1,75 @@
import { readFile, writeFile } from 'fs/promises' import { readFile, writeFile } from 'fs/promises';
import fs from 'fs' import fs from 'fs';
import { dirname, join, sep } from 'path' import { dirname, join, sep } from 'path';
import { fileURLToPath } from 'url' import { fileURLToPath } from 'url';
const currentDirname = dirname(fileURLToPath(import.meta.url)) const currentDirname = dirname(fileURLToPath(import.meta.url));
const toolName = process.argv[2] const toolName = process.argv[2];
const folder = process.argv[3] const folder = process.argv[3];
const toolsDir = join(currentDirname, '..', 'src', 'pages', folder ?? '') const toolsDir = join(
currentDirname,
'..',
'src',
'pages',
'tools',
folder ?? ''
);
if (!toolName) { if (!toolName) {
throw new Error('Please specify a toolname.') throw new Error('Please specify a toolname.');
} }
function capitalizeFirstLetter(string) { function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1) return string.charAt(0).toUpperCase() + string.slice(1);
} }
function createFolderStructure(basePath, foldersToCreateIndexCount) { function createFolderStructure(basePath, foldersToCreateIndexCount) {
const folderArray = basePath.split(sep) const folderArray = basePath.split(sep);
function recursiveCreate(currentBase, index) { function recursiveCreate(currentBase, index) {
if (index >= folderArray.length) { if (index >= folderArray.length) {
return return;
} }
const currentPath = join(currentBase, folderArray[index]) const currentPath = join(currentBase, folderArray[index]);
if (!fs.existsSync(currentPath)) { if (!fs.existsSync(currentPath)) {
fs.mkdirSync(currentPath, { recursive: true }) fs.mkdirSync(currentPath, { recursive: true });
} }
const indexPath = join(currentPath, 'index.ts') const indexPath = join(currentPath, 'index.ts');
if (!fs.existsSync(indexPath) && index < folderArray.length - 1 && index >= folderArray.length - 1 - foldersToCreateIndexCount) { if (
fs.writeFileSync(indexPath, `export const ${currentPath.split(sep)[currentPath.split(sep).length - 1]}Tools = [];\n`) !fs.existsSync(indexPath) &&
console.log(`File created: ${indexPath}`) index < folderArray.length - 1 &&
index >= folderArray.length - 1 - foldersToCreateIndexCount
) {
fs.writeFileSync(
indexPath,
`export const ${
currentPath.split(sep)[currentPath.split(sep).length - 1]
}Tools = [];\n`
);
console.log(`File created: ${indexPath}`);
} }
// Recursively create the next folder // Recursively create the next folder
recursiveCreate(currentPath, index + 1) recursiveCreate(currentPath, index + 1);
} }
// Start the recursive folder creation // Start the recursive folder creation
recursiveCreate('.', 0) recursiveCreate('.', 0);
} }
const toolNameCamelCase = toolName.replace(/-./g, (x) => x[1].toUpperCase()) const toolNameCamelCase = toolName.replace(/-./g, (x) => x[1].toUpperCase());
const toolNameTitleCase = const toolNameTitleCase =
toolName[0].toUpperCase() + toolName.slice(1).replace(/-/g, ' ') toolName[0].toUpperCase() + toolName.slice(1).replace(/-/g, ' ');
const toolDir = join(toolsDir, toolName) const toolDir = join(toolsDir, toolName);
const type = folder.split(sep)[folder.split(sep).length - 1] const type = folder.split(sep)[folder.split(sep).length - 1];
await createFolderStructure(toolDir, folder.split(sep).length) await createFolderStructure(toolDir, folder.split(sep).length);
console.log(`Directory created: ${toolDir}`) console.log(`Directory created: ${toolDir}`);
const createToolFile = async (name, content) => { const createToolFile = async (name, content) => {
const filePath = join(toolDir, name) const filePath = join(toolDir, name);
await writeFile(filePath, content.trim()) await writeFile(filePath, content.trim());
console.log(`File created: ${filePath}`) console.log(`File created: ${filePath}`);
} };
createToolFile( createToolFile(
`index.tsx`, `index.tsx`,
@@ -70,7 +86,7 @@ export default function ${capitalizeFirstLetter(toolNameCamelCase)}() {
return <Box>Lorem ipsum</Box>; return <Box>Lorem ipsum</Box>;
} }
` `
) );
createToolFile( createToolFile(
`meta.ts`, `meta.ts`,
` `
@@ -84,13 +100,13 @@ export const tool = defineTool('${type}', {
// image, // image,
description: '', description: '',
shortDescription: '', shortDescription: '',
keywords: ['${toolName.split('-').join('\', \'')}'], keywords: ['${toolName.split('-').join("', '")}'],
component: lazy(() => import('./index')) component: lazy(() => import('./index'))
}); });
` `
) );
createToolFile(`service.ts`, ``) createToolFile(`service.ts`, ``);
createToolFile( createToolFile(
`${toolName}.service.test.ts`, `${toolName}.service.test.ts`,
` `
@@ -101,7 +117,7 @@ import { expect, describe, it } from 'vitest';
// //
// }) // })
` `
) );
// createToolFile( // createToolFile(
// `${toolName}.e2e.spec.ts`, // `${toolName}.e2e.spec.ts`,
@@ -125,15 +141,17 @@ import { expect, describe, it } from 'vitest';
// ` // `
// ) // )
const toolsIndex = join(toolsDir, 'index.ts') const toolsIndex = join(toolsDir, 'index.ts');
const indexContent = await readFile(toolsIndex, { encoding: 'utf-8' }).then( const indexContent = await readFile(toolsIndex, { encoding: 'utf-8' }).then(
(r) => r.split('\n') (r) => r.split('\n')
) );
indexContent.splice( indexContent.splice(
0, 0,
0, 0,
`import { tool as ${type}${capitalizeFirstLetter(toolNameCamelCase)} } from './${toolName}/meta';` `import { tool as ${type}${capitalizeFirstLetter(
) toolNameCamelCase
writeFile(toolsIndex, indexContent.join('\n')) )} } from './${toolName}/meta';`
console.log(`Added import in: ${toolsIndex}`) );
writeFile(toolsIndex, indexContent.join('\n'));
console.log(`Added import in: ${toolsIndex}`);

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -71,10 +71,12 @@ export default function Hero() {
{...params} {...params}
fullWidth fullWidth
placeholder={'Search all tools'} placeholder={'Search all tools'}
sx={{ borderRadius: 2 }}
InputProps={{ InputProps={{
...params.InputProps, ...params.InputProps,
endAdornment: <SearchIcon /> endAdornment: <SearchIcon />,
sx: {
borderRadius: 4
}
}} }}
onChange={(event) => handleInputChange(event, event.target.value)} onChange={(event) => handleInputChange(event, event.target.value)}
/> />
@@ -112,7 +114,8 @@ export default function Hero() {
borderRadius: 3, borderRadius: 3,
borderColor: 'grey', borderColor: 'grey',
borderStyle: 'solid', borderStyle: 'solid',
cursor: 'pointer' cursor: 'pointer',
'&:hover': { backgroundColor: '#FAFAFD' }
}} }}
> >
<Typography>{tool.label}</Typography> <Typography>{tool.label}</Typography>

View File

@@ -1,12 +1,11 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import AppBar from '@mui/material/AppBar'; import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu'; import MenuIcon from '@mui/icons-material/Menu';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import githubIcon from '@assets/github-mark.png'; // Adjust the path to your GitHub icon import logo from 'assets/logo.png';
import { import {
Drawer, Drawer,
List, List,
@@ -22,7 +21,6 @@ const Navbar: React.FC = () => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md')); const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const [drawerOpen, setDrawerOpen] = useState(false); const [drawerOpen, setDrawerOpen] = useState(false);
const toggleDrawer = (open: boolean) => () => { const toggleDrawer = (open: boolean) => () => {
setDrawerOpen(open); setDrawerOpen(open);
}; };
@@ -55,17 +53,12 @@ const Navbar: React.FC = () => {
style={{ backgroundColor: 'white', color: 'black' }} style={{ backgroundColor: 'white', color: 'black' }}
> >
<Toolbar sx={{ justifyContent: 'space-between', alignItems: 'center' }}> <Toolbar sx={{ justifyContent: 'space-between', alignItems: 'center' }}>
<Typography <img
onClick={() => navigate('/')} onClick={() => navigate('/')}
fontSize={25} style={{ cursor: 'pointer' }}
sx={{ src={logo}
cursor: 'pointer', width={isMobile ? '80px' : '150px'}
textShadow: '1px 1px 2px rgba(0,0,0,0.2)' />
}}
color={'primary'}
>
OmniTools
</Typography>
{isMobile ? ( {isMobile ? (
<> <>
<IconButton <IconButton

View File

@@ -0,0 +1,86 @@
import { getToolsByCategory } from '@tools/index';
import Grid from '@mui/material/Grid';
import { Card, CardContent, Stack } from '@mui/material';
import { HugeiconsIcon } from '@hugeicons/react';
import { Link, useNavigate } from 'react-router-dom';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import { useState } from 'react';
type ArrayElement<ArrayType extends readonly unknown[]> =
ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
const SingleCategory = function ({
category,
index
}: {
category: ArrayElement<ReturnType<typeof getToolsByCategory>>;
index: number;
}) {
const navigate = useNavigate();
const [hovered, setHovered] = useState<boolean>(false);
const Icon = category.icon;
const toggleHover = () => setHovered((prevState) => !prevState);
return (
<Grid
item
xs={12}
md={6}
onMouseEnter={toggleHover}
onMouseLeave={toggleHover}
>
<Card
sx={{
height: '100%',
backgroundColor: hovered ? '#FAFAFD' : 'white'
}}
>
<CardContent>
<Stack direction={'row'} spacing={2} alignItems={'center'}>
<HugeiconsIcon
icon={Icon}
style={{
transform: `scale(${hovered ? 1.1 : 1}`
}}
color={categoriesColors[index % categoriesColors.length]}
/>
<Link
style={{ fontSize: 20, fontWeight: 700, color: 'black' }}
to={'/categories/' + category.type}
>
{category.title}
</Link>
</Stack>
<Typography sx={{ mt: 2 }}>{category.description}</Typography>
<Grid mt={1} container spacing={2}>
<Grid item xs={12} md={6}>
<Button
fullWidth
onClick={() => navigate('/categories/' + category.type)}
variant={'contained'}
>{`See all ${category.title}`}</Button>
</Grid>
<Grid item xs={12} md={6}>
<Button
sx={{ backgroundColor: 'white' }}
fullWidth
onClick={() => navigate(category.example.path)}
variant={'outlined'}
>{`Try ${category.example.title}`}</Button>
</Grid>
</Grid>
</CardContent>
</Card>
</Grid>
);
};
const categoriesColors: string[] = ['#8FBC5D', '#3CB6E2', '#FFD400', '#AB6993'];
export default function Categories() {
return (
<Grid width={'80%'} container mt={2} spacing={2}>
{getToolsByCategory().map((category, index) => (
<SingleCategory key={category.type} category={category} index={index} />
))}
</Grid>
);
}

View File

@@ -1,14 +1,8 @@
import { Box, Card, CardContent } from '@mui/material'; import { Box } from '@mui/material';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import { Link, useNavigate } from 'react-router-dom';
import { getToolsByCategory } from '../../tools';
import Button from '@mui/material/Button';
import Hero from 'components/Hero'; import Hero from 'components/Hero';
import Categories from './Categories';
export default function Home() { export default function Home() {
const navigate = useNavigate();
return ( return (
<Box <Box
padding={{ xs: 1, md: 3, lg: 5 }} padding={{ xs: 1, md: 3, lg: 5 }}
@@ -19,39 +13,7 @@ export default function Home() {
width={'100%'} width={'100%'}
> >
<Hero /> <Hero />
<Grid width={'80%'} container mt={2} spacing={2}> <Categories />
{getToolsByCategory().map((category) => (
<Grid key={category.type} item xs={12} md={6}>
<Card sx={{ height: '100%' }}>
<CardContent>
<Link
style={{ fontSize: 20 }}
to={'/categories/' + category.type}
>
{category.title}
</Link>
<Typography sx={{ mt: 2 }}>{category.description}</Typography>
<Grid mt={1} container spacing={2}>
<Grid item xs={12} md={6}>
<Button
fullWidth
onClick={() => navigate('/categories/' + category.type)}
variant={'contained'}
>{`See all ${category.title}`}</Button>
</Grid>
<Grid item xs={12} md={6}>
<Button
fullWidth
onClick={() => navigate(category.example.path)}
variant={'outlined'}
>{`Try ${category.example.title}`}</Button>
</Grid>
</Grid>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</Box> </Box>
); );
} }

View File

@@ -17,9 +17,9 @@ export const listTools = [
listFindUnique, listFindUnique,
listFindMostPopular, listFindMostPopular,
listGroup, listGroup,
listWrap, // listWrap,
listRotate, listRotate,
listShuffle, listShuffle
listTruncate, // listTruncate,
listDuplicate // listDuplicate
]; ];

View File

@@ -11,11 +11,11 @@ import { tool as stringJoin } from './join/meta';
export const stringTools = [ export const stringTools = [
stringSplit, stringSplit,
stringJoin, stringJoin,
stringToMorse, stringToMorse
stringReverse, // stringReverse,
stringRandomizeCase, // stringRandomizeCase,
stringUppercase, // stringUppercase,
stringExtractSubstring, // stringExtractSubstring,
stringCreatePalindrome, // stringCreatePalindrome,
stringPalindrome // stringPalindrome
]; ];

View File

@@ -6,42 +6,56 @@ import { numberTools } from '../pages/tools/number';
import { videoTools } from '../pages/tools/video'; import { videoTools } from '../pages/tools/video';
import { listTools } from '../pages/tools/list'; import { listTools } from '../pages/tools/list';
import { Entries } from 'type-fest'; import { Entries } from 'type-fest';
import {
ArrangeByNumbers19Icon,
Gif01Icon,
HugeiconsIcon,
LeftToRightListBulletIcon,
Png01Icon,
TextIcon
} from '@hugeicons/core-free-icons';
export const tools: DefinedTool[] = [ export const tools: DefinedTool[] = [
...imageTools, ...imageTools,
...stringTools, ...stringTools,
...numberTools, ...listTools,
...videoTools, ...videoTools,
...listTools ...numberTools
]; ];
const categoriesConfig: { const categoriesConfig: {
type: ToolCategory; type: ToolCategory;
value: string; value: string;
title?: string; title?: string;
icon: typeof HugeiconsIcon;
}[] = [ }[] = [
{ {
type: 'string', type: 'string',
title: 'Text', title: 'Text',
icon: TextIcon,
value: value:
'Tools for working with text convert text to images, find and replace text, split text into fragments, join text lines, repeat text, and much more.' 'Tools for working with text convert text to images, find and replace text, split text into fragments, join text lines, repeat text, and much more.'
}, },
{ {
type: 'png', type: 'png',
icon: Png01Icon,
value: value:
'Tools for working with PNG images convert PNGs to JPGs, create transparent PNGs, change PNG colors, crop, rotate, resize PNGs, and much more.' 'Tools for working with PNG images convert PNGs to JPGs, create transparent PNGs, change PNG colors, crop, rotate, resize PNGs, and much more.'
}, },
{ {
type: 'number', type: 'number',
icon: ArrangeByNumbers19Icon,
value: value:
'Tools for working with numbers generate number sequences, convert numbers to words and words to numbers, sort, round, factor numbers, and much more.' 'Tools for working with numbers generate number sequences, convert numbers to words and words to numbers, sort, round, factor numbers, and much more.'
}, },
{ {
type: 'gif', type: 'gif',
icon: Gif01Icon,
value: value:
'Tools for working with GIF animations create transparent GIFs, extract GIF frames, add text to GIF, crop, rotate, reverse GIFs, and much more.' 'Tools for working with GIF animations create transparent GIFs, extract GIF frames, add text to GIF, crop, rotate, reverse GIFs, and much more.'
}, },
{ {
type: 'list', type: 'list',
icon: LeftToRightListBulletIcon,
value: value:
'Tools for working with lists sort, reverse, randomize lists, find unique and duplicate list items, change list item separators, and much more.' 'Tools for working with lists sort, reverse, randomize lists, find unique and duplicate list items, change list item separators, and much more.'
} }
@@ -68,6 +82,7 @@ export const filterTools = (
export const getToolsByCategory = (): { export const getToolsByCategory = (): {
title: string; title: string;
description: string; description: string;
icon: typeof HugeiconsIcon;
type: string; type: string;
example: { title: string; path: string }; example: { title: string; path: string };
tools: DefinedTool[]; tools: DefinedTool[];
@@ -76,14 +91,14 @@ export const getToolsByCategory = (): {
Object.groupBy(tools, ({ type }) => type); Object.groupBy(tools, ({ type }) => type);
return (Object.entries(groupedByType) as Entries<typeof groupedByType>).map( return (Object.entries(groupedByType) as Entries<typeof groupedByType>).map(
([type, tools]) => { ([type, tools]) => {
const categoryConfig = categoriesConfig.find(
(config) => config.type === type
);
return { return {
title: `${ title: `${categoryConfig?.title ?? capitalizeFirstLetter(type)} Tools`,
categoriesConfig.find((config) => config.type === type)?.title ?? description: categoryConfig?.value ?? '',
capitalizeFirstLetter(type)
} Tools`,
description:
categoriesConfig.find((desc) => desc.type === type)?.value ?? '',
type, type,
icon: categoryConfig!.icon,
tools: tools ?? [], tools: tools ?? [],
example: tools example: tools
? { title: tools[0].name, path: tools[0].path } ? { title: tools[0].name, path: tools[0].path }