diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index da0021c..2d65c3a 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,10 +4,20 @@
-
+
+
+
+
+
+
+
+
+
+
+
@@ -78,7 +88,7 @@
"git-widget-placeholder": "main",
"ignore.virus.scanning.warn.message": "true",
"kotlin-language-version-configured": "true",
- "last_opened_file_path": "C:/Users/HP/IdeaProjects/omni-tools/src/components/options",
+ "last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/src/assets",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
@@ -111,11 +121,11 @@
+
-
@@ -250,15 +260,11 @@
-
-
-
-
- 1719282131977
-
-
-
- 1719282131977
+
+
+
+
+
@@ -644,7 +650,15 @@
1740267666455
-
+
+
+ 1740276092528
+
+
+
+ 1740276092528
+
+
@@ -676,7 +690,6 @@
-
@@ -701,7 +714,8 @@
-
+
+
diff --git a/README.md b/README.md
index 140dfbb..3d150f4 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,13 @@
-# OmniTools
+
+OmniTools
-Welcome to **OmniTools**, a self-hosted alternative to PineTools.com.
+[//]: # ([](https://hub.docker.com/r/iib0011/omni-tools))
-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
+[](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
diff --git a/package-lock.json b/package-lock.json
index 6fe2eef..5ac24ec 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index 37e3a8b..01ae12a 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/scripts/create-tool.mjs b/scripts/create-tool.mjs
index e57693a..b149588 100644
--- a/scripts/create-tool.mjs
+++ b/scripts/create-tool.mjs
@@ -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 Lorem ipsum;
}
`
-)
+);
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}`);
diff --git a/src/assets/logo.png b/src/assets/logo.png
new file mode 100644
index 0000000..559d193
Binary files /dev/null and b/src/assets/logo.png differ
diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx
index fb74b41..79521ee 100644
--- a/src/components/Hero.tsx
+++ b/src/components/Hero.tsx
@@ -71,10 +71,12 @@ export default function Hero() {
{...params}
fullWidth
placeholder={'Search all tools'}
- sx={{ borderRadius: 2 }}
InputProps={{
...params.InputProps,
- endAdornment:
+ endAdornment: ,
+ 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' }
}}
>
{tool.label}
diff --git a/src/components/Navbar/index.tsx b/src/components/Navbar/index.tsx
index 0060011..6e8e0e0 100644
--- a/src/components/Navbar/index.tsx
+++ b/src/components/Navbar/index.tsx
@@ -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' }}
>
- navigate('/')}
- fontSize={25}
- sx={{
- cursor: 'pointer',
- textShadow: '1px 1px 2px rgba(0,0,0,0.2)'
- }}
- color={'primary'}
- >
- OmniTools
-
+ style={{ cursor: 'pointer' }}
+ src={logo}
+ width={isMobile ? '80px' : '150px'}
+ />
{isMobile ? (
<>
=
+ ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
+
+const SingleCategory = function ({
+ category,
+ index
+}: {
+ category: ArrayElement>;
+ index: number;
+}) {
+ const navigate = useNavigate();
+ const [hovered, setHovered] = useState(false);
+ const Icon = category.icon;
+ const toggleHover = () => setHovered((prevState) => !prevState);
+ return (
+
+
+
+
+
+
+ {category.title}
+
+
+ {category.description}
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+const categoriesColors: string[] = ['#8FBC5D', '#3CB6E2', '#FFD400', '#AB6993'];
+export default function Categories() {
+ return (
+
+ {getToolsByCategory().map((category, index) => (
+
+ ))}
+
+ );
+}
diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx
index d9be29e..ba122b0 100644
--- a/src/pages/home/index.tsx
+++ b/src/pages/home/index.tsx
@@ -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 (
-
- {getToolsByCategory().map((category) => (
-
-
-
-
- {category.title}
-
- {category.description}
-
-
-
-
-
-
-
-
-
-
-
- ))}
-
+
);
}
diff --git a/src/pages/tools/list/index.ts b/src/pages/tools/list/index.ts
index d66d283..8a3c7bf 100644
--- a/src/pages/tools/list/index.ts
+++ b/src/pages/tools/list/index.ts
@@ -17,9 +17,9 @@ export const listTools = [
listFindUnique,
listFindMostPopular,
listGroup,
- listWrap,
+ // listWrap,
listRotate,
- listShuffle,
- listTruncate,
- listDuplicate
+ listShuffle
+ // listTruncate,
+ // listDuplicate
];
diff --git a/src/pages/tools/string/index.ts b/src/pages/tools/string/index.ts
index 4f29bd4..39f8d1f 100644
--- a/src/pages/tools/string/index.ts
+++ b/src/pages/tools/string/index.ts
@@ -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
];
diff --git a/src/tools/index.ts b/src/tools/index.ts
index 1b5e144..faed760 100644
--- a/src/tools/index.ts
+++ b/src/tools/index.ts
@@ -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).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 }