Merge remote-tracking branch 'origin/string-join'

# Conflicts:
#	.idea/workspace.xml
#	src/tools/defineTool.tsx
This commit is contained in:
Ibrahima G. Coulibaly
2024-06-25 22:12:04 +01:00
27 changed files with 730 additions and 143 deletions

100
src/components/Hero.tsx Normal file
View 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>
);
}

View File

@@ -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>

View File

@@ -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>
);

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View File

@@ -46,7 +46,7 @@ export default function ToolTextInput({
fileInputRef.current?.click();
};
return (
<Box id="tool">
<Box>
<InputHeader title={title} />
<TextField
value={value}

View File

@@ -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}
/>