mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-19 14:09:31 +02:00
Merge remote-tracking branch 'origin/string-join'
# Conflicts: # .idea/workspace.xml # src/tools/defineTool.tsx
This commit is contained in:
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}
|
||||
/>
|
||||
|
Reference in New Issue
Block a user