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" />
</component>
<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$/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/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>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -78,7 +88,7 @@
&quot;git-widget-placeholder&quot;: &quot;main&quot;,
&quot;ignore.virus.scanning.warn.message&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.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
@@ -111,11 +121,11 @@
</component>
<component name="RecentsManager">
<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\assets" />
<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\images" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\tools" />
@@ -250,15 +260,11 @@
<workItem from="1720912096050" duration="3065000" />
<workItem from="1740259920741" duration="7742000" />
<workItem from="1740270391152" duration="690000" />
<workItem from="1740274898695" duration="1168000" />
</task>
<task id="LOCAL-00054" summary="chore: sum tests">
<option name="closed" value="true" />
<created>1719282131977</created>
<option name="number" value="00054" />
<option name="presentableId" value="LOCAL-00054" />
<option name="project" value="LOCAL" />
<updated>1719282131977</updated>
<workItem from="1740274898695" duration="2231000" />
<workItem from="1740295530385" duration="1120000" />
<workItem from="1740300354462" duration="1059000" />
<workItem from="1740301493702" duration="8924000" />
<workItem from="1740318886545" duration="856000" />
</task>
<task id="LOCAL-00055" summary="fix: readme">
<option name="closed" value="true" />
@@ -644,7 +650,15 @@
<option name="project" value="LOCAL" />
<updated>1740267666455</updated>
</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 />
</component>
<component name="TypeScriptGeneratedFilesManager">
@@ -676,7 +690,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: remove unused deps" />
<MESSAGE value="style: lint" />
<MESSAGE value="fix: radio and list sort init" />
<MESSAGE value="chore: formik updateField" />
@@ -701,7 +714,8 @@
<MESSAGE value="refactor: optimize imports" />
<MESSAGE value="chore: use string tools" />
<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 name="XSLT-Support.FileAssociations.UIState">
<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,
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
[![Discord](https://img.shields.io/discord/1342971141823664179?label=Discord)](https://discord.gg/SDbbn3hT4b)
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.
Here is the [demo](https://omnitools.netlify.app/) website.
@@ -19,27 +23,43 @@ Here is the [demo](https://omnitools.netlify.app/) website.
## 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
@@ -49,6 +69,8 @@ docker run -d --name omni-tools --restart unless-stopped -p 8080:80 iib0011/omni
## Contribute
This is a React Project with Typescript Material UI.
### Project setup
```bash

16
package-lock.json generated
View File

@@ -10,6 +10,8 @@
"dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@hugeicons/core-free-icons": "^1.0.10",
"@hugeicons/react": "^1.0.3",
"@mui/icons-material": "^5.15.20",
"@mui/material": "^5.15.20",
"@playwright/test": "^1.45.0",
@@ -1398,6 +1400,20 @@
"@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": {
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",

View File

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

View File

@@ -1,59 +1,75 @@
import { readFile, writeFile } from 'fs/promises'
import fs from 'fs'
import { dirname, join, sep } from 'path'
import { fileURLToPath } from 'url'
import { readFile, writeFile } from 'fs/promises';
import fs from 'fs';
import { dirname, join, sep } from 'path';
import { fileURLToPath } from 'url';
const currentDirname = dirname(fileURLToPath(import.meta.url))
const currentDirname = dirname(fileURLToPath(import.meta.url));
const toolName = process.argv[2]
const folder = process.argv[3]
const toolName = process.argv[2];
const folder = process.argv[3];
const toolsDir = join(currentDirname, '..', 'src', 'pages', folder ?? '')
const toolsDir = join(
currentDirname,
'..',
'src',
'pages',
'tools',
folder ?? ''
);
if (!toolName) {
throw new Error('Please specify a toolname.')
throw new Error('Please specify a toolname.');
}
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1)
return string.charAt(0).toUpperCase() + string.slice(1);
}
function createFolderStructure(basePath, foldersToCreateIndexCount) {
const folderArray = basePath.split(sep)
const folderArray = basePath.split(sep);
function recursiveCreate(currentBase, index) {
if (index >= folderArray.length) {
return
return;
}
const currentPath = join(currentBase, folderArray[index])
const currentPath = join(currentBase, folderArray[index]);
if (!fs.existsSync(currentPath)) {
fs.mkdirSync(currentPath, { recursive: true })
fs.mkdirSync(currentPath, { recursive: true });
}
const indexPath = join(currentPath, 'index.ts')
if (!fs.existsSync(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}`)
const indexPath = join(currentPath, 'index.ts');
if (
!fs.existsSync(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
recursiveCreate(currentPath, index + 1)
recursiveCreate(currentPath, index + 1);
}
// 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 =
toolName[0].toUpperCase() + toolName.slice(1).replace(/-/g, ' ')
const toolDir = join(toolsDir, toolName)
const type = folder.split(sep)[folder.split(sep).length - 1]
await createFolderStructure(toolDir, folder.split(sep).length)
console.log(`Directory created: ${toolDir}`)
toolName[0].toUpperCase() + toolName.slice(1).replace(/-/g, ' ');
const toolDir = join(toolsDir, toolName);
const type = folder.split(sep)[folder.split(sep).length - 1];
await createFolderStructure(toolDir, folder.split(sep).length);
console.log(`Directory created: ${toolDir}`);
const createToolFile = async (name, content) => {
const filePath = join(toolDir, name)
await writeFile(filePath, content.trim())
console.log(`File created: ${filePath}`)
}
const filePath = join(toolDir, name);
await writeFile(filePath, content.trim());
console.log(`File created: ${filePath}`);
};
createToolFile(
`index.tsx`,
@@ -70,7 +86,7 @@ export default function ${capitalizeFirstLetter(toolNameCamelCase)}() {
return <Box>Lorem ipsum</Box>;
}
`
)
);
createToolFile(
`meta.ts`,
`
@@ -84,13 +100,13 @@ export const tool = defineTool('${type}', {
// image,
description: '',
shortDescription: '',
keywords: ['${toolName.split('-').join('\', \'')}'],
keywords: ['${toolName.split('-').join("', '")}'],
component: lazy(() => import('./index'))
});
`
)
);
createToolFile(`service.ts`, ``)
createToolFile(`service.ts`, ``);
createToolFile(
`${toolName}.service.test.ts`,
`
@@ -101,7 +117,7 @@ import { expect, describe, it } from 'vitest';
//
// })
`
)
);
// createToolFile(
// `${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(
(r) => r.split('\n')
)
);
indexContent.splice(
0,
0,
`import { tool as ${type}${capitalizeFirstLetter(toolNameCamelCase)} } from './${toolName}/meta';`
)
writeFile(toolsIndex, indexContent.join('\n'))
console.log(`Added import in: ${toolsIndex}`)
`import { tool as ${type}${capitalizeFirstLetter(
toolNameCamelCase
)} } from './${toolName}/meta';`
);
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}
fullWidth
placeholder={'Search all tools'}
sx={{ borderRadius: 2 }}
InputProps={{
...params.InputProps,
endAdornment: <SearchIcon />
endAdornment: <SearchIcon />,
sx: {
borderRadius: 4
}
}}
onChange={(event) => handleInputChange(event, event.target.value)}
/>
@@ -112,7 +114,8 @@ export default function Hero() {
borderRadius: 3,
borderColor: 'grey',
borderStyle: 'solid',
cursor: 'pointer'
cursor: 'pointer',
'&:hover': { backgroundColor: '#FAFAFD' }
}}
>
<Typography>{tool.label}</Typography>

View File

@@ -1,12 +1,11 @@
import React, { useState } from 'react';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';
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 {
Drawer,
List,
@@ -22,7 +21,6 @@ const Navbar: React.FC = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const [drawerOpen, setDrawerOpen] = useState(false);
const toggleDrawer = (open: boolean) => () => {
setDrawerOpen(open);
};
@@ -55,17 +53,12 @@ const Navbar: React.FC = () => {
style={{ backgroundColor: 'white', color: 'black' }}
>
<Toolbar sx={{ justifyContent: 'space-between', alignItems: 'center' }}>
<Typography
<img
onClick={() => navigate('/')}
fontSize={25}
sx={{
cursor: 'pointer',
textShadow: '1px 1px 2px rgba(0,0,0,0.2)'
}}
color={'primary'}
>
OmniTools
</Typography>
style={{ cursor: 'pointer' }}
src={logo}
width={isMobile ? '80px' : '150px'}
/>
{isMobile ? (
<>
<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 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 { Box } from '@mui/material';
import Hero from 'components/Hero';
import Categories from './Categories';
export default function Home() {
const navigate = useNavigate();
return (
<Box
padding={{ xs: 1, md: 3, lg: 5 }}
@@ -19,39 +13,7 @@ export default function Home() {
width={'100%'}
>
<Hero />
<Grid width={'80%'} container mt={2} spacing={2}>
{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>
<Categories />
</Box>
);
}

View File

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

View File

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

View File

@@ -6,42 +6,56 @@ import { numberTools } from '../pages/tools/number';
import { videoTools } from '../pages/tools/video';
import { listTools } from '../pages/tools/list';
import { Entries } from 'type-fest';
import {
ArrangeByNumbers19Icon,
Gif01Icon,
HugeiconsIcon,
LeftToRightListBulletIcon,
Png01Icon,
TextIcon
} from '@hugeicons/core-free-icons';
export const tools: DefinedTool[] = [
...imageTools,
...stringTools,
...numberTools,
...listTools,
...videoTools,
...listTools
...numberTools
];
const categoriesConfig: {
type: ToolCategory;
value: string;
title?: string;
icon: typeof HugeiconsIcon;
}[] = [
{
type: 'string',
title: 'Text',
icon: TextIcon,
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.'
},
{
type: 'png',
icon: Png01Icon,
value:
'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',
icon: ArrangeByNumbers19Icon,
value:
'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',
icon: Gif01Icon,
value:
'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',
icon: LeftToRightListBulletIcon,
value:
'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 = (): {
title: string;
description: string;
icon: typeof HugeiconsIcon;
type: string;
example: { title: string; path: string };
tools: DefinedTool[];
@@ -76,14 +91,14 @@ export const getToolsByCategory = (): {
Object.groupBy(tools, ({ type }) => type);
return (Object.entries(groupedByType) as Entries<typeof groupedByType>).map(
([type, tools]) => {
const categoryConfig = categoriesConfig.find(
(config) => config.type === type
);
return {
title: `${
categoriesConfig.find((config) => config.type === type)?.title ??
capitalizeFirstLetter(type)
} Tools`,
description:
categoriesConfig.find((desc) => desc.type === type)?.value ?? '',
title: `${categoryConfig?.title ?? capitalizeFirstLetter(type)} Tools`,
description: categoryConfig?.value ?? '',
type,
icon: categoryConfig!.icon,
tools: tools ?? [],
example: tools
? { title: tools[0].name, path: tools[0].path }