diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 1890e3c..3400181 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,11 +4,20 @@
-
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
@@ -37,37 +46,38 @@
- {
- "keyToString": {
- "ASKED_ADD_EXTERNAL_FILES": "true",
- "ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
- "RunOnceActivity.OpenProjectViewOnStart": "true",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "Vitest.mergeText.executor": "Run",
- "Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor": "Run",
- "Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor": "Run",
- "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/assets",
- "node.js.detected.package.eslint": "true",
- "node.js.detected.package.tslint": "true",
- "node.js.selected.package.eslint": "(autodetect)",
- "node.js.selected.package.tslint": "(autodetect)",
- "nodejs_package_manager_path": "npm",
- "npm.dev.executor": "Run",
- "npm.prebuild.executor": "Run",
- "npm.script:create:tool.executor": "Run",
- "npm.test.executor": "Run",
- "prettierjs.PrettierConfiguration.Package": "C:\\Users\\HP\\IdeaProjects\\omni-tools\\node_modules\\prettier",
- "project.structure.last.edited": "Problems",
- "project.structure.proportion": "0.0",
- "project.structure.side.proportion": "0.2",
- "settings.editor.selected.configurable": "settings.typescriptcompiler",
- "ts.external.directory.path": "C:\\Users\\HP\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib",
- "vue.rearranger.settings.migration": "true"
+
+}]]>
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -134,6 +157,7 @@
+
@@ -163,7 +187,7 @@
-
+
@@ -549,7 +573,15 @@
1719274243788
-
+
+
+ 1719275214988
+
+
+
+ 1719275214988
+
+
@@ -570,7 +602,6 @@
-
@@ -595,7 +626,8 @@
-
+
+
diff --git a/package-lock.json b/package-lock.json
index 7c7f076..d332906 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,9 +13,11 @@
"@mui/icons-material": "^5.15.20",
"@mui/material": "^5.15.20",
"@types/lodash": "^4.17.5",
+ "@types/morsee": "^1.0.2",
"color": "^4.2.3",
"formik": "^2.4.6",
"lodash": "^4.17.21",
+ "morsee": "^1.0.9",
"notistack": "^3.0.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -2476,6 +2478,11 @@
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.5.tgz",
"integrity": "sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw=="
},
+ "node_modules/@types/morsee": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@types/morsee/-/morsee-1.0.2.tgz",
+ "integrity": "sha512-WANv1kCyQtmGZTiov9FzFdt1X4wRtXYZA6B4YR3CghKgx4ychU7d1gkOx7oD+ddVGI+SWmWOPccco7pAc6wXeA=="
+ },
"node_modules/@types/node": {
"version": "20.14.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.6.tgz",
@@ -6194,6 +6201,11 @@
"ufo": "^1.5.3"
}
},
+ "node_modules/morsee": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/morsee/-/morsee-1.0.9.tgz",
+ "integrity": "sha512-8X8jKVUmZBHKpET9Ap6FPiwlAAASvv60M1K25/YwCU7veuj5MfYgaWX3oEPHtMGgC44IIkIKzyD73fduEKB/9g=="
+ },
"node_modules/mrmime": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
diff --git a/package.json b/package.json
index 71c3c43..0034b7f 100644
--- a/package.json
+++ b/package.json
@@ -29,9 +29,11 @@
"@mui/icons-material": "^5.15.20",
"@mui/material": "^5.15.20",
"@types/lodash": "^4.17.5",
+ "@types/morsee": "^1.0.2",
"color": "^4.2.3",
"formik": "^2.4.6",
"lodash": "^4.17.21",
+ "morsee": "^1.0.9",
"notistack": "^3.0.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
diff --git a/scripts/create-tool.mjs b/scripts/create-tool.mjs
index 86390c9..a6f5c12 100644
--- a/scripts/create-tool.mjs
+++ b/scripts/create-tool.mjs
@@ -45,7 +45,7 @@ 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}`)
@@ -78,7 +78,7 @@ import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
// import image from '@assets/text.png';
-export const tool = defineTool('${folder.split(sep)[folder.split(sep).length - 1]}', {
+export const tool = defineTool('${type}', {
name: '${toolNameTitleCase}',
path: '${toolName}',
// image,
@@ -132,7 +132,7 @@ const indexContent = await readFile(toolsIndex, { encoding: 'utf-8' }).then(
indexContent.splice(
0,
0,
- `import { tool as ${toolNameCamelCase} } from './${toolName}/meta';`
+ `import { tool as ${type}${capitalizeFirstLetter(toolNameCamelCase)} } from './${toolName}/meta';`
)
writeFile(toolsIndex, indexContent.join('\n'))
console.log(`Added import in: ${toolsIndex}`)
diff --git a/src/components/options/ToolOptionGroups.tsx b/src/components/options/ToolOptionGroups.tsx
new file mode 100644
index 0000000..ed7f465
--- /dev/null
+++ b/src/components/options/ToolOptionGroups.tsx
@@ -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 (
+
+ {groups.map((group) => (
+
+ {group.title}
+ {group.component}
+
+ ))}
+
+ );
+}
diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx
index d5a3e87..beb5f81 100644
--- a/src/pages/home/index.tsx
+++ b/src/pages/home/index.tsx
@@ -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: '' },
diff --git a/src/pages/string/index.ts b/src/pages/string/index.ts
index b53d52c..3011347 100644
--- a/src/pages/string/index.ts
+++ b/src/pages/string/index.ts
@@ -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];
diff --git a/src/pages/string/join/index.tsx b/src/pages/string/join/index.tsx
index bb4792f..cc4778a 100644
--- a/src/pages/string/join/index.tsx
+++ b/src/pages/string/join/index.tsx
@@ -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 }) => (
-
- Text Merged Options
-
- setFieldValue(mergeOptions.accessor, value)
+
+ setFieldValue(mergeOptions.accessor, value)
+ }
+ description={mergeOptions.description}
+ />
+ )
+ },
+ {
+ title: 'Blank Lines and Trailing Spaces',
+ component: blankTrailingOptions.map((option) => (
+
+ setFieldValue(option.accessor, value)
+ }
+ description={option.description}
+ />
+ ))
}
- description={mergeOptions.description}
- />
-
-
-
- Blank Lines and Trailing Spaces
-
- {blankTrailingOptions.map((option) => (
- setFieldValue(option.accessor, value)}
- description={option.description}
- />
- ))}
-
+ ]}
+ />
)}
diff --git a/src/pages/string/split/index.tsx b/src/pages/string/split/index.tsx
index c28e229..8664495 100644
--- a/src/pages/string/split/index.tsx
+++ b/src/pages/string/split/index.tsx
@@ -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 }) => (
-
- Split separator options
- {splitOperators.map(({ title, description, type }) => (
-
- setFieldValue('splitSeparatorType', type)
- }
- onTextChange={(val) => setFieldValue(`${type}Value`, val)}
- />
- ))}
-
-
- Output separator options
- {outputOptions.map((option) => (
- setFieldValue(option.accessor, value)}
- description={option.description}
- />
- ))}
-
+ (
+
+ setFieldValue('splitSeparatorType', type)
+ }
+ onTextChange={(val) =>
+ setFieldValue(`${type}Value`, val)
+ }
+ />
+ )
+ )
+ },
+ {
+ title: 'Output separator options',
+ component: outputOptions.map((option) => (
+
+ setFieldValue(option.accessor, value)
+ }
+ description={option.description}
+ />
+ ))
+ }
+ ]}
+ />
)}
diff --git a/src/pages/string/to-morse/index.tsx b/src/pages/string/to-morse/index.tsx
new file mode 100644
index 0000000..3f92301
--- /dev/null
+++ b/src/pages/string/to-morse/index.tsx
@@ -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('');
+ const [result, setResult] = useState('');
+ // const formRef = useRef>(null);
+ const { showSnackBar } = useContext(CustomSnackBarContext);
+
+ const FormikListenerComponent = () => {
+ const { values } = useFormikContext();
+
+ 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 (
+
+
+
+
+
+
+
+
+
+
+ {}}
+ >
+ {({ setFieldValue, values }) => (
+
+
+ setFieldValue('dotSymbol', val)}
+ />
+ )
+ },
+ {
+ title: 'Long Signal',
+ component: (
+ setFieldValue('dashSymbol', val)}
+ />
+ )
+ }
+ ]}
+ />
+
+ )}
+
+
+
+ );
+}
diff --git a/src/pages/string/to-morse/meta.ts b/src/pages/string/to-morse/meta.ts
new file mode 100644
index 0000000..cc7c51e
--- /dev/null
+++ b/src/pages/string/to-morse/meta.ts
@@ -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'))
+});
diff --git a/src/pages/string/to-morse/service.ts b/src/pages/string/to-morse/service.ts
new file mode 100644
index 0000000..6f81b6e
--- /dev/null
+++ b/src/pages/string/to-morse/service.ts
@@ -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);
+};
diff --git a/src/pages/string/to-morse/to-morse.service.test.ts b/src/pages/string/to-morse/to-morse.service.test.ts
new file mode 100644
index 0000000..6f87b54
--- /dev/null
+++ b/src/pages/string/to-morse/to-morse.service.test.ts
@@ -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);
+ });
+});