mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-20 06:29:32 +02:00
feat: string to morse
This commit is contained in:
20
src/components/options/ToolOptionGroups.tsx
Normal file
20
src/components/options/ToolOptionGroups.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import Typography from '@mui/material/Typography';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { Box, Stack } from '@mui/material';
|
||||
|
||||
export default function ToolOptionGroups({
|
||||
groups
|
||||
}: {
|
||||
groups: { title: string; component: ReactNode }[];
|
||||
}) {
|
||||
return (
|
||||
<Stack direction={'row'} spacing={2}>
|
||||
{groups.map((group) => (
|
||||
<Box key={group.title}>
|
||||
<Typography fontSize={22}>{group.title}</Typography>
|
||||
{group.component}
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
}
|
@@ -12,7 +12,7 @@ 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 { DefinedTool } from '@tools/defineTool';
|
||||
import Button from '@mui/material/Button';
|
||||
|
||||
const exampleTools: { label: string; url: string }[] = [
|
||||
@@ -20,7 +20,7 @@ const exampleTools: { label: string; url: string }[] = [
|
||||
label: 'Create a transparent image',
|
||||
url: ''
|
||||
},
|
||||
{ label: 'Convert text to morse code', url: '' },
|
||||
{ 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: '' },
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { tool as stringToMorse } from './to-morse/meta';
|
||||
import { tool as stringSplit } from './split/meta';
|
||||
import { tool as stringJoin } from './join/meta';
|
||||
|
||||
export const stringTools = [stringSplit, stringJoin];
|
||||
export const stringTools = [stringSplit, stringJoin, stringToMorse];
|
||||
|
@@ -9,6 +9,7 @@ import { mergeText } from './service';
|
||||
import { CustomSnackBarContext } from '../../../contexts/CustomSnackBarContext';
|
||||
import TextFieldWithDesc from '../../../components/options/TextFieldWithDesc';
|
||||
import CheckboxWithDesc from '../../../components/options/CheckboxWithDesc';
|
||||
import ToolOptionGroups from '../../../components/options/ToolOptionGroups';
|
||||
|
||||
const initialValues = {
|
||||
joinCharacter: '',
|
||||
@@ -90,31 +91,37 @@ export default function JoinText() {
|
||||
{({ setFieldValue, values }) => (
|
||||
<Stack direction={'row'} spacing={2}>
|
||||
<FormikListenerComponent input={input} />
|
||||
<Box>
|
||||
<Typography fontSize={22}>Text Merged Options</Typography>
|
||||
<TextFieldWithDesc
|
||||
placeholder={mergeOptions.placeholder}
|
||||
value={values['joinCharacter']}
|
||||
onChange={(value) =>
|
||||
setFieldValue(mergeOptions.accessor, value)
|
||||
<ToolOptionGroups
|
||||
groups={[
|
||||
{
|
||||
title: 'Text Merged Options',
|
||||
component: (
|
||||
<TextFieldWithDesc
|
||||
placeholder={mergeOptions.placeholder}
|
||||
value={values['joinCharacter']}
|
||||
onChange={(value) =>
|
||||
setFieldValue(mergeOptions.accessor, value)
|
||||
}
|
||||
description={mergeOptions.description}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Blank Lines and Trailing Spaces',
|
||||
component: blankTrailingOptions.map((option) => (
|
||||
<CheckboxWithDesc
|
||||
key={option.accessor}
|
||||
title={option.title}
|
||||
checked={!!values[option.accessor]}
|
||||
onChange={(value) =>
|
||||
setFieldValue(option.accessor, value)
|
||||
}
|
||||
description={option.description}
|
||||
/>
|
||||
))
|
||||
}
|
||||
description={mergeOptions.description}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography fontSize={22}>
|
||||
Blank Lines and Trailing Spaces
|
||||
</Typography>
|
||||
{blankTrailingOptions.map((option) => (
|
||||
<CheckboxWithDesc
|
||||
key={option.accessor}
|
||||
title={option.title}
|
||||
checked={!!values[option.accessor]}
|
||||
onChange={(value) => setFieldValue(option.accessor, value)}
|
||||
description={option.description}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
]}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
</Formik>
|
||||
|
@@ -1,16 +1,16 @@
|
||||
import { Box, Stack, TextField } from '@mui/material';
|
||||
import { Box, Stack } from '@mui/material';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import ToolTextInput from '../../../components/input/ToolTextInput';
|
||||
import ToolTextResult from '../../../components/result/ToolTextResult';
|
||||
import { Field, Formik, FormikProps, useFormikContext } from 'formik';
|
||||
import { Formik, useFormikContext } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import ToolOptions from '../../../components/options/ToolOptions';
|
||||
import { compute, SplitOperatorType } from './service';
|
||||
import { CustomSnackBarContext } from '../../../contexts/CustomSnackBarContext';
|
||||
import RadioWithTextField from '../../../components/options/RadioWithTextField';
|
||||
import TextFieldWithDesc from '../../../components/options/TextFieldWithDesc';
|
||||
import ToolOptionGroups from '../../../components/options/ToolOptionGroups';
|
||||
|
||||
const initialValues = {
|
||||
splitSeparatorType: 'symbol' as SplitOperatorType,
|
||||
@@ -143,34 +143,44 @@ export default function SplitText() {
|
||||
{({ setFieldValue, values }) => (
|
||||
<Stack direction={'row'} spacing={2}>
|
||||
<FormikListenerComponent />
|
||||
<Box>
|
||||
<Typography fontSize={22}>Split separator options</Typography>
|
||||
{splitOperators.map(({ title, description, type }) => (
|
||||
<RadioWithTextField
|
||||
key={type}
|
||||
type={type}
|
||||
title={title}
|
||||
fieldName={'splitSeparatorType'}
|
||||
description={description}
|
||||
value={values[`${type}Value`]}
|
||||
onTypeChange={(type) =>
|
||||
setFieldValue('splitSeparatorType', type)
|
||||
}
|
||||
onTextChange={(val) => setFieldValue(`${type}Value`, val)}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography fontSize={22}>Output separator options</Typography>
|
||||
{outputOptions.map((option) => (
|
||||
<TextFieldWithDesc
|
||||
key={option.accessor}
|
||||
value={values[option.accessor]}
|
||||
onChange={(value) => setFieldValue(option.accessor, value)}
|
||||
description={option.description}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
<ToolOptionGroups
|
||||
groups={[
|
||||
{
|
||||
title: 'Split separator options',
|
||||
component: splitOperators.map(
|
||||
({ title, description, type }) => (
|
||||
<RadioWithTextField
|
||||
key={type}
|
||||
type={type}
|
||||
title={title}
|
||||
fieldName={'splitSeparatorType'}
|
||||
description={description}
|
||||
value={values[`${type}Value`]}
|
||||
onTypeChange={(type) =>
|
||||
setFieldValue('splitSeparatorType', type)
|
||||
}
|
||||
onTextChange={(val) =>
|
||||
setFieldValue(`${type}Value`, val)
|
||||
}
|
||||
/>
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Output separator options',
|
||||
component: outputOptions.map((option) => (
|
||||
<TextFieldWithDesc
|
||||
key={option.accessor}
|
||||
value={values[option.accessor]}
|
||||
onChange={(value) =>
|
||||
setFieldValue(option.accessor, value)
|
||||
}
|
||||
description={option.description}
|
||||
/>
|
||||
))
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
</Formik>
|
||||
|
98
src/pages/string/to-morse/index.tsx
Normal file
98
src/pages/string/to-morse/index.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import { Box, Stack } from '@mui/material';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import ToolTextInput from '../../../components/input/ToolTextInput';
|
||||
import ToolTextResult from '../../../components/result/ToolTextResult';
|
||||
import { Formik, useFormikContext } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import ToolOptions from '../../../components/options/ToolOptions';
|
||||
import { compute } from './service';
|
||||
import { CustomSnackBarContext } from '../../../contexts/CustomSnackBarContext';
|
||||
import TextFieldWithDesc from '../../../components/options/TextFieldWithDesc';
|
||||
import ToolOptionGroups from '../../../components/options/ToolOptionGroups';
|
||||
|
||||
const initialValues = {
|
||||
dotSymbol: '.',
|
||||
dashSymbol: '-'
|
||||
};
|
||||
|
||||
export default function ToMorse() {
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
// const formRef = useRef<FormikProps<typeof initialValues>>(null);
|
||||
const { showSnackBar } = useContext(CustomSnackBarContext);
|
||||
|
||||
const FormikListenerComponent = () => {
|
||||
const { values } = useFormikContext<typeof initialValues>();
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const { dotSymbol, dashSymbol } = values;
|
||||
|
||||
setResult(compute(input, dotSymbol, dashSymbol));
|
||||
} catch (exception: unknown) {
|
||||
if (exception instanceof Error)
|
||||
showSnackBar(exception.message, 'error');
|
||||
}
|
||||
}, [values, input]);
|
||||
|
||||
return null; // This component doesn't render anything
|
||||
};
|
||||
const validationSchema = Yup.object({
|
||||
// splitSeparator: Yup.string().required('The separator is required')
|
||||
});
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={6}>
|
||||
<ToolTextInput value={input} onChange={setInput} />
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<ToolTextResult title={'Morse code'} value={result} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<ToolOptions>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={() => {}}
|
||||
>
|
||||
{({ setFieldValue, values }) => (
|
||||
<Stack direction={'row'} spacing={2}>
|
||||
<FormikListenerComponent />
|
||||
<ToolOptionGroups
|
||||
groups={[
|
||||
{
|
||||
title: 'Short Signal',
|
||||
component: (
|
||||
<TextFieldWithDesc
|
||||
description={
|
||||
'Symbol that will correspond to the dot in Morse code.'
|
||||
}
|
||||
value={values.dotSymbol}
|
||||
onChange={(val) => setFieldValue('dotSymbol', val)}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Long Signal',
|
||||
component: (
|
||||
<TextFieldWithDesc
|
||||
description={
|
||||
'Symbol that will correspond to the dash in Morse code.'
|
||||
}
|
||||
value={values.dashSymbol}
|
||||
onChange={(val) => setFieldValue('dashSymbol', val)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
</Formik>
|
||||
</ToolOptions>
|
||||
</Box>
|
||||
);
|
||||
}
|
12
src/pages/string/to-morse/meta.ts
Normal file
12
src/pages/string/to-morse/meta.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { defineTool } from '@tools/defineTool';
|
||||
import { lazy } from 'react';
|
||||
// import image from '@assets/text.png';
|
||||
|
||||
export const tool = defineTool('string', {
|
||||
name: 'String To morse',
|
||||
path: 'to-morse',
|
||||
// image,
|
||||
description: '',
|
||||
keywords: ['to', 'morse'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
9
src/pages/string/to-morse/service.ts
Normal file
9
src/pages/string/to-morse/service.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { encode } from 'morsee';
|
||||
|
||||
export const compute = (
|
||||
input: string,
|
||||
dotSymbol: string,
|
||||
dashSymbol: string
|
||||
): string => {
|
||||
return encode(input).replaceAll('.', dotSymbol).replaceAll('-', dashSymbol);
|
||||
};
|
50
src/pages/string/to-morse/to-morse.service.test.ts
Normal file
50
src/pages/string/to-morse/to-morse.service.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { compute } from './service';
|
||||
|
||||
describe('compute function', () => {
|
||||
it('should replace dots and dashes with specified symbols', () => {
|
||||
const input = 'test';
|
||||
const dotSymbol = '*';
|
||||
const dashSymbol = '#';
|
||||
const result = compute(input, dotSymbol, dashSymbol);
|
||||
const expected = '# * *** #';
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should return an empty string for empty input', () => {
|
||||
const input = '';
|
||||
const dotSymbol = '*';
|
||||
const dashSymbol = '#';
|
||||
const result = compute(input, dotSymbol, dashSymbol);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
// Test case 3: Special characters handling
|
||||
it('should handle input with special characters', () => {
|
||||
const input = 'hello, world!';
|
||||
const dotSymbol = '*';
|
||||
const dashSymbol = '#';
|
||||
const result = compute(input, dotSymbol, dashSymbol);
|
||||
const expected =
|
||||
'**** * *#** *#** ### ##**## / *## ### *#* *#** #** #*#*##';
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should work with different symbols for dots and dashes', () => {
|
||||
const input = 'morse';
|
||||
const dotSymbol = '!';
|
||||
const dashSymbol = '@';
|
||||
const result = compute(input, dotSymbol, dashSymbol);
|
||||
const expected = '@@ @@@ !@! !!! !';
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle numeric input correctly', () => {
|
||||
const input = '12345';
|
||||
const dotSymbol = '*';
|
||||
const dashSymbol = '#';
|
||||
const result = compute(input, dotSymbol, dashSymbol);
|
||||
const expected = '*#### **### ***## ****# *****'; // This depends on how "12345" is encoded in morse code
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user