mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-17 21:19:36 +02:00
Merge remote-tracking branch 'origin/string-join'
# Conflicts: # .idea/workspace.xml # src/tools/defineTool.tsx
This commit is contained in:
@@ -83,6 +83,7 @@ export const tool = defineTool('${type}', {
|
||||
path: '${toolName}',
|
||||
// image,
|
||||
description: '',
|
||||
shortDescription: '',
|
||||
keywords: ['${toolName.split('-').join('\', \'')}'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
||||
|
BIN
src/assets/tools.png
Normal file
BIN
src/assets/tools.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
100
src/components/Hero.tsx
Normal file
100
src/components/Hero.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Autocomplete, Box, Stack, TextField } from '@mui/material';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import { useState } from 'react';
|
||||
import { DefinedTool } from '@tools/defineTool';
|
||||
import { filterTools, tools } from '@tools/index';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const exampleTools: { label: string; url: string }[] = [
|
||||
{
|
||||
label: 'Create a transparent image',
|
||||
url: '/png/create-transparent'
|
||||
},
|
||||
{ label: 'Convert text to morse code', url: '/string/to-morse' },
|
||||
{ label: 'Change GIF speed', url: '' },
|
||||
{ label: 'Pick a random item', url: '' },
|
||||
{ label: 'Find and replace text', url: '' },
|
||||
{ label: 'Convert emoji to image', url: '' },
|
||||
{ label: 'Split a string', url: '/string/split' },
|
||||
{ label: 'Calculate number sum', url: '/number/sum' },
|
||||
{ label: 'Pixelate an image', url: '' }
|
||||
];
|
||||
export default function Hero() {
|
||||
const [inputValue, setInputValue] = useState<string>('');
|
||||
const [filteredTools, setFilteredTools] = useState<DefinedTool[]>(tools);
|
||||
const navigate = useNavigate();
|
||||
const handleInputChange = (
|
||||
event: React.ChangeEvent<{}>,
|
||||
newInputValue: string
|
||||
) => {
|
||||
setInputValue(newInputValue);
|
||||
setFilteredTools(filterTools(tools, newInputValue));
|
||||
};
|
||||
return (
|
||||
<Box width={'60%'}>
|
||||
<Stack mb={1} direction={'row'} spacing={1}>
|
||||
<Typography fontSize={30}>Transform Your Workflow with </Typography>
|
||||
<Typography fontSize={30} color={'primary'}>
|
||||
Omni Tools
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Typography fontSize={20} mb={2}>
|
||||
Boost your productivity with Omni Tools, the ultimate toolkit for
|
||||
getting things done quickly! Access thousands of user-friendly utilities
|
||||
for editing images, text, lists, and data, all directly from your
|
||||
browser.
|
||||
</Typography>
|
||||
|
||||
<Autocomplete
|
||||
sx={{ mb: 2 }}
|
||||
autoHighlight
|
||||
options={filteredTools}
|
||||
getOptionLabel={(option) => option.name}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
fullWidth
|
||||
placeholder={'Search all tools'}
|
||||
sx={{ borderRadius: 2 }}
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
endAdornment: <SearchIcon />
|
||||
}}
|
||||
onChange={(event) => handleInputChange(event, event.target.value)}
|
||||
/>
|
||||
)}
|
||||
renderOption={(props, option) => (
|
||||
<Box component="li" {...props} onClick={() => navigate(option.path)}>
|
||||
<Box>
|
||||
<Typography fontWeight={'bold'}>{option.name}</Typography>
|
||||
<Typography fontSize={12}>{option.shortDescription}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
/>
|
||||
<Grid container spacing={2} mt={2}>
|
||||
{exampleTools.map((tool) => (
|
||||
<Grid onClick={() => navigate(tool.url)} item xs={4} key={tool.label}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
padding: 1,
|
||||
borderRadius: 3,
|
||||
borderColor: 'grey',
|
||||
borderStyle: 'solid',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
<Typography>{tool.label}</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -9,7 +9,7 @@ export default function ToolInputAndResult({
|
||||
result: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Grid container spacing={2}>
|
||||
<Grid id="tool" container spacing={2}>
|
||||
<Grid item xs={6}>
|
||||
{input}
|
||||
</Grid>
|
||||
|
@@ -2,6 +2,10 @@ import { Box } from '@mui/material';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import ToolHeader from './ToolHeader';
|
||||
import Separator from '@tools/Separator';
|
||||
import AllTools from './allTools/AllTools';
|
||||
import { getToolsByCategory } from '@tools/index';
|
||||
import { capitalizeFirstLetter } from '../utils/string';
|
||||
|
||||
export default function ToolLayout({
|
||||
children,
|
||||
@@ -15,7 +19,18 @@ export default function ToolLayout({
|
||||
image?: string;
|
||||
type: string;
|
||||
children: ReactNode;
|
||||
type: string;
|
||||
}) {
|
||||
const otherCategoryTools =
|
||||
getToolsByCategory()
|
||||
.find((category) => category.type === type)
|
||||
?.tools.filter((tool) => tool.name !== title)
|
||||
.map((tool) => ({
|
||||
title: tool.name,
|
||||
description: tool.shortDescription,
|
||||
link: '/' + tool.path
|
||||
})) ?? [];
|
||||
|
||||
return (
|
||||
<Box
|
||||
width={'100%'}
|
||||
@@ -34,6 +49,11 @@ export default function ToolLayout({
|
||||
type={type}
|
||||
/>
|
||||
{children}
|
||||
<Separator backgroundColor="#5581b5" margin="50px" />
|
||||
<AllTools
|
||||
title={`All ${capitalizeFirstLetter(type)} tools`}
|
||||
toolCards={otherCategoryTools}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
36
src/components/allTools/AllTools.tsx
Normal file
36
src/components/allTools/AllTools.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Box, Grid, Stack, Typography } from '@mui/material';
|
||||
import ToolCard from './ToolCard';
|
||||
|
||||
export interface ToolCardProps {
|
||||
title: string;
|
||||
description: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
interface AllToolsProps {
|
||||
title: string;
|
||||
toolCards: ToolCardProps[];
|
||||
}
|
||||
|
||||
export default function AllTools({ title, toolCards }: AllToolsProps) {
|
||||
return (
|
||||
<Box mt={4} mb={10}>
|
||||
<Typography mb={2} fontSize={30} color={'primary'}>
|
||||
{title}
|
||||
</Typography>
|
||||
<Stack direction={'row'} alignItems={'center'} spacing={2}>
|
||||
<Grid container spacing={2}>
|
||||
{toolCards.map((card, index) => (
|
||||
<Grid item xs={4} key={index}>
|
||||
<ToolCard
|
||||
title={card.title}
|
||||
description={card.description}
|
||||
link={card.link}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
44
src/components/allTools/ToolCard.tsx
Normal file
44
src/components/allTools/ToolCard.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Box, Link, Card, CardContent, Typography } from '@mui/material';
|
||||
import { ToolCardProps } from './AllTools';
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export default function ToolCard({ title, description, link }: ToolCardProps) {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<Card
|
||||
onClick={() => navigate(link)}
|
||||
raised
|
||||
sx={{
|
||||
borderRadius: 2,
|
||||
bgcolor: '#5581b5',
|
||||
borderColor: '#5581b5',
|
||||
color: '#fff',
|
||||
boxShadow: '6px 6px 12px #b8b9be, -6px -6px 12px #fff',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
sx={{
|
||||
paddingBottom: 1,
|
||||
borderBottomWidth: 1,
|
||||
borderColor: '#ffffff70'
|
||||
}}
|
||||
>
|
||||
<Typography variant="h5" component="h2">
|
||||
{title}
|
||||
</Typography>
|
||||
<Link href={link} underline="none" sx={{ color: '#fff' }}>
|
||||
<ChevronRightIcon />
|
||||
</Link>
|
||||
</Box>
|
||||
<Typography variant="body2" mt={2} color="#fff">
|
||||
{description}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
114
src/components/examples/ExampleCard.tsx
Normal file
114
src/components/examples/ExampleCard.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import { ExampleCardProps } from './Examples';
|
||||
import {
|
||||
Box,
|
||||
Stack,
|
||||
Card,
|
||||
CardContent,
|
||||
Typography,
|
||||
TextField,
|
||||
useTheme
|
||||
} from '@mui/material';
|
||||
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
||||
import RequiredOptions from './RequiredOptions';
|
||||
|
||||
export default function ExampleCard({
|
||||
title,
|
||||
description,
|
||||
sampleText,
|
||||
sampleResult,
|
||||
requiredOptions,
|
||||
changeInputResult
|
||||
}: ExampleCardProps) {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Card
|
||||
raised
|
||||
sx={{
|
||||
bgcolor: theme.palette.background.default,
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
borderRadius: 2,
|
||||
transition: 'background-color 0.3s ease',
|
||||
'&:hover': {
|
||||
boxShadow: '12px 9px 11px 2px #b8b9be, -6px -6px 12px #fff'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<Box display="flex" justifyContent="space-between" borderRadius="5px">
|
||||
<Typography variant="h5" component="h2">
|
||||
{title}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack direction={'column'} alignItems={'center'} spacing={2}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{description}
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
onClick={() => changeInputResult(sampleText, sampleResult)}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
zIndex: '2',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
bgcolor: 'transparent',
|
||||
padding: '5px 10px',
|
||||
borderRadius: '5px',
|
||||
cursor: 'pointer',
|
||||
boxShadow: 'inset 2px 2px 5px #b8b9be, inset -3px -3px 7px #fff;'
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
value={sampleText}
|
||||
disabled
|
||||
fullWidth
|
||||
multiline
|
||||
sx={{
|
||||
'& .MuiOutlinedInput-root': {
|
||||
zIndex: '-1',
|
||||
'& fieldset': {
|
||||
border: 'none'
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<ArrowDownwardIcon />
|
||||
<Box
|
||||
onClick={() => changeInputResult(sampleText, sampleResult)}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
zIndex: '2',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
bgcolor: 'transparent',
|
||||
padding: '5px 10px',
|
||||
borderRadius: '5px',
|
||||
cursor: 'pointer',
|
||||
boxShadow: 'inset 2px 2px 5px #b8b9be, inset -3px -3px 7px #fff;'
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
value={sampleResult}
|
||||
disabled
|
||||
fullWidth
|
||||
multiline
|
||||
sx={{
|
||||
'& .MuiOutlinedInput-root': {
|
||||
zIndex: '-1',
|
||||
'& fieldset': {
|
||||
border: 'none'
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<RequiredOptions options={requiredOptions} />
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
59
src/components/examples/Examples.tsx
Normal file
59
src/components/examples/Examples.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Box, Grid, Stack, Typography } from '@mui/material';
|
||||
import ExampleCard from './ExampleCard';
|
||||
|
||||
export interface ExampleCardProps {
|
||||
title: string;
|
||||
description: string;
|
||||
sampleText: string;
|
||||
sampleResult: string;
|
||||
requiredOptions: RequiredOptionsProps;
|
||||
changeInputResult: (input: string, result: string) => void;
|
||||
}
|
||||
|
||||
export interface RequiredOptionsProps {
|
||||
joinCharacter: string;
|
||||
deleteBlankLines: boolean;
|
||||
deleteTrailingSpaces: boolean;
|
||||
}
|
||||
|
||||
interface ExampleProps {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
exampleCards: ExampleCardProps[];
|
||||
}
|
||||
|
||||
export default function Examples({
|
||||
title,
|
||||
subtitle,
|
||||
exampleCards
|
||||
}: ExampleProps) {
|
||||
return (
|
||||
<Box id={'examples'} mt={4}>
|
||||
<Box mt={4} display="flex" gap={1} alignItems="center">
|
||||
<Typography mb={2} fontSize={30} color={'primary'}>
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography mb={2} fontSize={30} color={'secondary'}>
|
||||
{subtitle}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Stack direction={'row'} alignItems={'center'} spacing={2}>
|
||||
<Grid container spacing={2}>
|
||||
{exampleCards.map((card, index) => (
|
||||
<Grid item xs={4} key={index}>
|
||||
<ExampleCard
|
||||
title={card.title}
|
||||
description={card.description}
|
||||
sampleText={card.sampleText}
|
||||
sampleResult={card.sampleResult}
|
||||
requiredOptions={card.requiredOptions}
|
||||
changeInputResult={card.changeInputResult}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
78
src/components/examples/RequiredOptions.tsx
Normal file
78
src/components/examples/RequiredOptions.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Box, Stack, TextField, Typography } from '@mui/material';
|
||||
import { RequiredOptionsProps } from './Examples';
|
||||
import CheckboxWithDesc from 'components/options/CheckboxWithDesc';
|
||||
|
||||
export default function RequiredOptions({
|
||||
options
|
||||
}: {
|
||||
options: RequiredOptionsProps;
|
||||
}) {
|
||||
const { joinCharacter, deleteBlankLines, deleteTrailingSpaces } = options;
|
||||
|
||||
const handleBoxClick = () => {
|
||||
const toolsElement = document.getElementById('tool');
|
||||
if (toolsElement) {
|
||||
toolsElement.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack direction={'column'} alignItems={'left'} spacing={2}>
|
||||
<Typography variant="h5" component="h3" sx={{ marginTop: '5px' }}>
|
||||
Required options
|
||||
</Typography>
|
||||
<Typography variant="body2" component="p">
|
||||
These options will be used automatically if you select this example.
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
onClick={handleBoxClick}
|
||||
sx={{
|
||||
zIndex: '2',
|
||||
cursor: 'pointer',
|
||||
bgcolor: 'transparent',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex'
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
disabled
|
||||
value={joinCharacter}
|
||||
fullWidth
|
||||
rows={1}
|
||||
sx={{
|
||||
'& .MuiOutlinedInput-root': {
|
||||
zIndex: '-1'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{deleteBlankLines ? (
|
||||
<Box onClick={handleBoxClick}>
|
||||
<CheckboxWithDesc
|
||||
title="Delete Blank Lines"
|
||||
checked={deleteBlankLines}
|
||||
onChange={() => {}}
|
||||
description="Delete lines that don't have text symbols."
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{deleteTrailingSpaces ? (
|
||||
<Box onClick={handleBoxClick}>
|
||||
<CheckboxWithDesc
|
||||
title="Delete Training Spaces"
|
||||
checked={deleteTrailingSpaces}
|
||||
onChange={() => {}}
|
||||
description="Remove spaces and tabs at the end of the lines."
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
@@ -46,7 +46,7 @@ export default function ToolTextInput({
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
return (
|
||||
<Box id="tool">
|
||||
<Box>
|
||||
<InputHeader title={title} />
|
||||
<TextField
|
||||
value={value}
|
||||
|
@@ -5,12 +5,14 @@ const CheckboxWithDesc = ({
|
||||
title,
|
||||
description,
|
||||
checked,
|
||||
onChange
|
||||
onChange,
|
||||
disabled
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
checked: boolean;
|
||||
onChange: (value: boolean) => void;
|
||||
disabled?: boolean;
|
||||
}) => {
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(event.target.checked);
|
||||
@@ -20,7 +22,11 @@ const CheckboxWithDesc = ({
|
||||
<Box>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox defaultChecked checked={checked} onChange={handleChange} />
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
onChange={handleChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
}
|
||||
label={title}
|
||||
/>
|
||||
|
@@ -3,12 +3,17 @@ import { Navigate } from 'react-router-dom';
|
||||
import { lazy } from 'react';
|
||||
|
||||
const Home = lazy(() => import('../pages/home'));
|
||||
const ToolsByCategory = lazy(() => import('../pages/tools-by-category'));
|
||||
|
||||
const routes: RouteObject[] = [
|
||||
{
|
||||
path: '/',
|
||||
element: <Home />
|
||||
},
|
||||
{
|
||||
path: '/categories/:categoryName',
|
||||
element: <ToolsByCategory />
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
element: <Navigate to="404" />
|
||||
|
@@ -1,45 +1,14 @@
|
||||
import {
|
||||
Autocomplete,
|
||||
Box,
|
||||
Card,
|
||||
CardContent,
|
||||
Stack,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
import { Box, Card, CardContent, Stack } from '@mui/material';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { filterTools, getToolsByCategory, tools } from '../../tools';
|
||||
import { useState } from 'react';
|
||||
import { DefinedTool } from '@tools/defineTool';
|
||||
import { getToolsByCategory } from '../../tools';
|
||||
import Button from '@mui/material/Button';
|
||||
import Hero from 'components/Hero';
|
||||
|
||||
const exampleTools: { label: string; url: string }[] = [
|
||||
{
|
||||
label: 'Create a transparent image',
|
||||
url: '/png/create-transparent'
|
||||
},
|
||||
{ label: 'Convert text to morse code', url: '/string/to-morse' },
|
||||
{ label: 'Change GIF speed', url: '' },
|
||||
{ label: 'Pick a random item', url: '' },
|
||||
{ label: 'Find and replace text', url: '' },
|
||||
{ label: 'Convert emoji to image', url: '' },
|
||||
{ label: 'Split a string', url: '/string/split' },
|
||||
{ label: 'Calculate number sum', url: '/number/sum' },
|
||||
{ label: 'Pixelate an image', url: '' }
|
||||
];
|
||||
export default function Home() {
|
||||
const navigate = useNavigate();
|
||||
const [inputValue, setInputValue] = useState<string>('');
|
||||
const [filteredTools, setFilteredTools] = useState<DefinedTool[]>(tools);
|
||||
const handleInputChange = (
|
||||
event: React.ChangeEvent<{}>,
|
||||
newInputValue: string
|
||||
) => {
|
||||
setInputValue(newInputValue);
|
||||
setFilteredTools(filterTools(tools, newInputValue));
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
padding={5}
|
||||
@@ -49,108 +18,38 @@ export default function Home() {
|
||||
justifyContent={'center'}
|
||||
width={'100%'}
|
||||
>
|
||||
<Box width={'60%'}>
|
||||
<Stack mb={1} direction={'row'} spacing={1}>
|
||||
<Typography fontSize={30}>Transform Your Workflow with </Typography>
|
||||
<Typography fontSize={30} color={'primary'}>
|
||||
Omni Tools
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Typography fontSize={20} mb={2}>
|
||||
Boost your productivity with Omni Tools, the ultimate toolkit for
|
||||
getting things done quickly! Access thousands of user-friendly
|
||||
utilities for editing images, text, lists, and data, all directly from
|
||||
your browser.
|
||||
</Typography>
|
||||
|
||||
<Autocomplete
|
||||
sx={{ mb: 2 }}
|
||||
autoHighlight
|
||||
options={filteredTools}
|
||||
getOptionLabel={(option) => option.name}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
fullWidth
|
||||
placeholder={'Search all tools'}
|
||||
sx={{ borderRadius: 2 }}
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
endAdornment: <SearchIcon />
|
||||
}}
|
||||
onChange={(event) => handleInputChange(event, event.target.value)}
|
||||
/>
|
||||
)}
|
||||
renderOption={(props, option) => (
|
||||
<Box
|
||||
component="li"
|
||||
{...props}
|
||||
onClick={() => navigate(option.path)}
|
||||
>
|
||||
<Box>
|
||||
<Typography fontWeight={'bold'}>{option.name}</Typography>
|
||||
<Typography fontSize={12}>{option.description}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
/>
|
||||
<Grid container spacing={2} mt={2}>
|
||||
{exampleTools.map((tool) => (
|
||||
<Grid
|
||||
onClick={() => navigate(tool.url)}
|
||||
item
|
||||
xs={4}
|
||||
key={tool.label}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
padding: 1,
|
||||
borderRadius: 3,
|
||||
borderColor: 'grey',
|
||||
borderStyle: 'solid',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
<Typography>{tool.label}</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
<Grid container mt={2} spacing={2}>
|
||||
{getToolsByCategory().map((category) => (
|
||||
<Grid key={category.type} item xs={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Link
|
||||
style={{ fontSize: 20 }}
|
||||
to={'/categories/' + category.type}
|
||||
>
|
||||
{category.title}
|
||||
</Link>
|
||||
<Typography sx={{ mt: 2 }}>{category.description}</Typography>
|
||||
<Stack
|
||||
mt={2}
|
||||
direction={'row'}
|
||||
justifyContent={'space-between'}
|
||||
>
|
||||
<Button
|
||||
variant={'contained'}
|
||||
>{`See all ${category.title}`}</Button>
|
||||
<Button
|
||||
onClick={() => navigate(category.example.path)}
|
||||
variant={'outlined'}
|
||||
>{`Try ${category.example.title}`}</Button>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
<Hero />
|
||||
<Grid width={'80%'} container mt={2} spacing={2}>
|
||||
{getToolsByCategory().map((category) => (
|
||||
<Grid key={category.type} item xs={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Link
|
||||
style={{ fontSize: 20 }}
|
||||
to={'/categories/' + category.type}
|
||||
>
|
||||
{category.title}
|
||||
</Link>
|
||||
<Typography sx={{ mt: 2 }}>{category.description}</Typography>
|
||||
<Stack
|
||||
mt={2}
|
||||
direction={'row'}
|
||||
justifyContent={'space-between'}
|
||||
>
|
||||
<Button
|
||||
onClick={() => navigate('/categories/' + category.type)}
|
||||
variant={'contained'}
|
||||
>{`See all ${category.title}`}</Button>
|
||||
<Button
|
||||
onClick={() => navigate(category.example.path)}
|
||||
variant={'outlined'}
|
||||
>{`Try ${category.example.title}`}</Button>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ export const tool = defineTool('png', {
|
||||
image,
|
||||
description:
|
||||
"World's simplest online Portable Network Graphics (PNG) color changer. Just import your PNG image in the editor on the left, select which colors to change, and you'll instantly get a new PNG with the new colors on the right. Free, quick, and very powerful. Import a PNG – replace its colors.",
|
||||
shortDescription: 'Quickly swap colors in a PNG image',
|
||||
keywords: ['change', 'colors', 'in', 'png'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
||||
|
@@ -6,6 +6,7 @@ export const tool = defineTool('png', {
|
||||
name: 'Create transparent PNG',
|
||||
path: 'create-transparent',
|
||||
image,
|
||||
shortDescription: 'Quickly make a PNG image transparent',
|
||||
description:
|
||||
"World's simplest online Portable Network Graphics transparency maker. Just import your PNG image in the editor on the left and you will instantly get a transparent PNG on the right. Free, quick, and very powerful. Import a PNG – get a transparent PNG.",
|
||||
keywords: ['create', 'transparent'],
|
||||
|
@@ -8,6 +8,7 @@ export const tool = defineTool('number', {
|
||||
// image,
|
||||
description:
|
||||
'Quickly calculate the sum of numbers in your browser. To get your sum, just enter your list of numbers in the input field, adjust the separator between the numbers in the options below, and this utility will add up all these numbers.',
|
||||
shortDescription: 'Quickly sum numbers',
|
||||
keywords: ['sum'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
||||
|
19
src/pages/string/join/Info.tsx
Normal file
19
src/pages/string/join/Info.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Box, Stack, Typography } from '@mui/material';
|
||||
|
||||
interface ExampleProps {
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default function Example({ title, description }: ExampleProps) {
|
||||
return (
|
||||
<Stack direction={'row'} alignItems={'center'} spacing={2} mt={4}>
|
||||
<Box>
|
||||
<Typography mb={2} fontSize={30} color={'primary'}>
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography fontSize={20}>{description}</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
}
|
@@ -12,6 +12,11 @@ import CheckboxWithDesc from '../../../components/options/CheckboxWithDesc';
|
||||
import ToolOptionGroups from '../../../components/options/ToolOptionGroups';
|
||||
import ToolInputAndResult from '../../../components/ToolInputAndResult';
|
||||
|
||||
import Info from './Info';
|
||||
import Separator from '../../../tools/Separator';
|
||||
import AllTools from '../../../components/allTools/AllTools';
|
||||
import Examples from '../../../components/examples/Examples';
|
||||
|
||||
const initialValues = {
|
||||
joinCharacter: '',
|
||||
deleteBlank: true,
|
||||
@@ -48,6 +53,66 @@ const blankTrailingOptions: {
|
||||
}
|
||||
];
|
||||
|
||||
const exampleCards = [
|
||||
{
|
||||
title: 'Merge a To-Do List',
|
||||
description:
|
||||
"In this example, we merge a bullet point list into one sentence, separating each item by the word 'and'. We also remove all empty lines and trailing spaces. If we didn't remove the empty lines, then they'd be joined with the separator word, making the separator word appear multiple times. If we didn't remove the trailing tabs and spaces, then they'd create extra spacing in the joined text and it wouldn't look nice.",
|
||||
sampleText: `clean the house
|
||||
|
||||
go shopping
|
||||
feed the cat
|
||||
|
||||
make dinner
|
||||
build a rocket ship and fly away`,
|
||||
sampleResult: `clean the house and go shopping and feed the cat and make dinner and build a rocket ship and fly away`,
|
||||
requiredOptions: {
|
||||
joinCharacter: 'and',
|
||||
deleteBlankLines: true,
|
||||
deleteTrailingSpaces: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Comma Separated List',
|
||||
description:
|
||||
'This example joins a column of words into a comma separated list of words.',
|
||||
sampleText: `computer
|
||||
memory
|
||||
processor
|
||||
mouse
|
||||
keyboard`,
|
||||
sampleResult: `computer, memory, processor, mouse, keyboard`,
|
||||
requiredOptions: {
|
||||
joinCharacter: ',',
|
||||
deleteBlankLines: false,
|
||||
deleteTrailingSpaces: false
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Vertical Word to Horizontal',
|
||||
description:
|
||||
'This example rotates words from a vertical position to horizontal. An empty separator is used for this purpose.',
|
||||
sampleText: `T
|
||||
e
|
||||
x
|
||||
t
|
||||
a
|
||||
b
|
||||
u
|
||||
l
|
||||
o
|
||||
u
|
||||
s
|
||||
!`,
|
||||
sampleResult: `Textabulous!`,
|
||||
requiredOptions: {
|
||||
joinCharacter: '',
|
||||
deleteBlankLines: false,
|
||||
deleteTrailingSpaces: false
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export default function JoinText() {
|
||||
const [input, setInput] = useState<string>('');
|
||||
const { showSnackBar } = useContext(CustomSnackBarContext);
|
||||
@@ -69,6 +134,16 @@ export default function JoinText() {
|
||||
return null;
|
||||
};
|
||||
|
||||
function changeInputResult(input: string, result: string) {
|
||||
setInput(input);
|
||||
setResult(result);
|
||||
|
||||
const toolsElement = document.getElementById('tool');
|
||||
if (toolsElement) {
|
||||
toolsElement.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<ToolInputAndResult
|
||||
@@ -125,6 +200,19 @@ export default function JoinText() {
|
||||
)}
|
||||
</Formik>
|
||||
</ToolOptions>
|
||||
<Info
|
||||
title="What Is a Text Joiner?"
|
||||
description="With this tool you can join parts of the text together. It takes a list of text values, separated by newlines, and merges them together. You can set the character that will be placed between the parts of the combined text. Also, you can ignore all empty lines and remove spaces and tabs at the end of all lines. Textabulous!"
|
||||
/>
|
||||
<Separator backgroundColor="#5581b5" margin="50px" />
|
||||
<Examples
|
||||
title="Text Joiner Examples"
|
||||
subtitle="Click to try!"
|
||||
exampleCards={exampleCards.map((card) => ({
|
||||
...card,
|
||||
changeInputResult
|
||||
}))}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ export const tool = defineTool('string', {
|
||||
image,
|
||||
description:
|
||||
"World's Simplest Text Tool World's simplest browser-based utility for joining text. Load your text in the input form on the left and you'll automatically get merged text on the right. Powerful, free, and fast. Load text – get joined lines",
|
||||
shortDescription: 'Quickly merge texts',
|
||||
keywords: ['text', 'join'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
||||
|
@@ -8,6 +8,7 @@ export const tool = defineTool('string', {
|
||||
image,
|
||||
description:
|
||||
"World's simplest browser-based utility for splitting text. Load your text in the input form on the left and you'll automatically get pieces of this text on the right. Powerful, free, and fast. Load text – get chunks.",
|
||||
shortDescription: 'Quickly split a text',
|
||||
keywords: ['text', 'split'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
||||
|
@@ -8,6 +8,7 @@ export const tool = defineTool('string', {
|
||||
// image,
|
||||
description:
|
||||
"World's simplest browser-based utility for converting text to Morse code. Load your text in the input form on the left and you'll instantly get Morse code in the output area. Powerful, free, and fast. Load text – get Morse code.",
|
||||
shortDescription: 'Quickly encode text to morse',
|
||||
keywords: ['to', 'morse'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
||||
|
74
src/pages/tools-by-category/index.tsx
Normal file
74
src/pages/tools-by-category/index.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
CardContent,
|
||||
Divider,
|
||||
Stack,
|
||||
useTheme
|
||||
} from '@mui/material';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom';
|
||||
import { getToolsByCategory, tools } from '../../tools';
|
||||
import Button from '@mui/material/Button';
|
||||
import Hero from 'components/Hero';
|
||||
import AllTools from '../../components/allTools/AllTools';
|
||||
import { capitalizeFirstLetter } from '../../utils/string';
|
||||
import toolsPng from '@assets/tools.png';
|
||||
|
||||
export default function Home() {
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const { categoryName } = useParams();
|
||||
return (
|
||||
<Box>
|
||||
<Box
|
||||
padding={5}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
width={'100%'}
|
||||
>
|
||||
<Hero />
|
||||
</Box>
|
||||
<Divider sx={{ borderColor: theme.palette.primary.main }} />
|
||||
<Box width={'100%'} mt={3} ml={7} padding={3}>
|
||||
<Typography
|
||||
fontSize={22}
|
||||
color={theme.palette.primary.main}
|
||||
>{`All ${capitalizeFirstLetter(categoryName)} Tools`}</Typography>
|
||||
<Grid container spacing={2} mt={2}>
|
||||
{getToolsByCategory()
|
||||
.find(({ type }) => type === categoryName)
|
||||
?.tools?.map((tool) => (
|
||||
<Grid item xs={12} md={4} key={tool.path}>
|
||||
<Stack
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.background.default // Change this to your desired hover color
|
||||
}
|
||||
}}
|
||||
onClick={() => navigate('/' + tool.path)}
|
||||
direction={'row'}
|
||||
spacing={2}
|
||||
padding={2}
|
||||
border={1}
|
||||
borderRadius={2}
|
||||
>
|
||||
<img width={100} src={tool.image ?? toolsPng} />
|
||||
<Box>
|
||||
<Link to={'/' + tool.path}>{tool.name}</Link>
|
||||
<Typography sx={{ mt: 2 }}>
|
||||
{tool.shortDescription}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
23
src/tools/Separator.tsx
Normal file
23
src/tools/Separator.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Divider } from '@mui/material';
|
||||
import React from 'react';
|
||||
|
||||
type SeparatorProps = {
|
||||
backgroundColor: string;
|
||||
margin: string;
|
||||
};
|
||||
|
||||
export default function Separator({ backgroundColor, margin }: SeparatorProps) {
|
||||
return (
|
||||
<Divider
|
||||
orientation="horizontal"
|
||||
variant="fullWidth"
|
||||
className="my-4"
|
||||
sx={{
|
||||
backgroundColor: backgroundColor,
|
||||
height: '2px',
|
||||
marginTop: margin,
|
||||
marginBottom: margin
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
@@ -8,6 +8,7 @@ interface ToolOptions {
|
||||
image?: string;
|
||||
name: string;
|
||||
description: string;
|
||||
shortDescription: string;
|
||||
}
|
||||
|
||||
export interface DefinedTool {
|
||||
@@ -15,6 +16,7 @@ export interface DefinedTool {
|
||||
path: string;
|
||||
name: string;
|
||||
description: string;
|
||||
shortDescription: string;
|
||||
image?: string;
|
||||
keywords: string[];
|
||||
component: () => JSX.Element;
|
||||
@@ -24,7 +26,15 @@ export const defineTool = (
|
||||
basePath: string,
|
||||
options: ToolOptions
|
||||
): DefinedTool => {
|
||||
const { image, path, name, description, keywords, component } = options;
|
||||
const {
|
||||
image,
|
||||
path,
|
||||
name,
|
||||
description,
|
||||
keywords,
|
||||
component,
|
||||
shortDescription
|
||||
} = options;
|
||||
const Component = component;
|
||||
return {
|
||||
type: basePath,
|
||||
@@ -32,6 +42,7 @@ export const defineTool = (
|
||||
name,
|
||||
image,
|
||||
description,
|
||||
shortDescription,
|
||||
keywords,
|
||||
component: () => {
|
||||
return (
|
||||
|
@@ -38,6 +38,7 @@ export const filterTools = (
|
||||
(tool) =>
|
||||
tool.name.toLowerCase().includes(lowerCaseQuery) ||
|
||||
tool.description.toLowerCase().includes(lowerCaseQuery) ||
|
||||
tool.shortDescription.toLowerCase().includes(lowerCaseQuery) ||
|
||||
tool.keywords.some((keyword) =>
|
||||
keyword.toLowerCase().includes(lowerCaseQuery)
|
||||
)
|
||||
@@ -49,6 +50,7 @@ export const getToolsByCategory = (): {
|
||||
description: string;
|
||||
type: string;
|
||||
example: { title: string; path: string };
|
||||
tools: DefinedTool[];
|
||||
}[] => {
|
||||
const grouped: Partial<Record<string, DefinedTool[]>> = Object.groupBy(
|
||||
tools,
|
||||
@@ -60,6 +62,7 @@ export const getToolsByCategory = (): {
|
||||
description:
|
||||
categoriesDescriptions.find((desc) => desc.type === type)?.value ?? '',
|
||||
type,
|
||||
tools: tls ?? [],
|
||||
example: tls
|
||||
? { title: tls[0].name, path: tls[0].path }
|
||||
: { title: '', path: '' }
|
||||
|
@@ -1,3 +1,4 @@
|
||||
export function capitalizeFirstLetter(string: string) {
|
||||
export function capitalizeFirstLetter(string: string | undefined) {
|
||||
if (!string) return '';
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
Reference in New Issue
Block a user