refactor: tools folder inside pages

This commit is contained in:
Ibrahima G. Coulibaly
2025-02-23 01:38:42 +01:00
parent 62f084eb45
commit 64936ab11f
117 changed files with 447 additions and 194 deletions

View File

@@ -0,0 +1,66 @@
import { describe, expect } from 'vitest';
import { createPalindrome, createPalindromeList } from './service';
describe('createPalindrome', () => {
test('should create palindrome by reversing the entire string', () => {
const input = 'hello';
const result = createPalindrome(input, true);
expect(result).toBe('helloolleh');
});
test('should create palindrome by reversing the string excluding the last character', () => {
const input = 'hello';
const result = createPalindrome(input, false);
expect(result).toBe('hellolleh');
});
test('should return an empty string if input is empty', () => {
const input = '';
const result = createPalindrome(input, true);
expect(result).toBe('');
});
});
describe('createPalindromeList', () => {
test('should create palindrome for single-line input', () => {
const input = 'hello';
const result = createPalindromeList(input, true, false);
expect(result).toBe('helloolleh');
});
test('should create palindrome for single-line input considering trailing spaces', () => {
const input = 'hello ';
const result = createPalindromeList(input, true, false);
expect(result).toBe('hello olleh');
});
test('should create palindrome for single-line input ignoring trailing spaces if lastChar is set to false', () => {
const input = 'hello ';
const result = createPalindromeList(input, true, false);
expect(result).toBe('hello olleh');
});
test('should create palindrome for multi-line input', () => {
const input = 'hello\nworld';
const result = createPalindromeList(input, true, true);
expect(result).toBe('helloolleh\nworlddlrow');
});
test('should create palindrome for no multi-line input', () => {
const input = 'hello\nworld\n';
const result = createPalindromeList(input, true, false);
expect(result).toBe('hello\nworld\n\ndlrow\nolleh');
});
test('should handle multi-line input with lastChar set to false', () => {
const input = 'hello\nworld';
const result = createPalindromeList(input, false, true);
expect(result).toBe('hellolleh\nworldlrow');
});
test('should return an empty string if input is empty', () => {
const input = '';
const result = createPalindromeList(input, true, false);
expect(result).toBe('');
});
});

View File

@@ -0,0 +1,11 @@
import { Box } from '@mui/material';
import React from 'react';
import * as Yup from 'yup';
const initialValues = {};
const validationSchema = Yup.object({
// splitSeparator: Yup.string().required('The separator is required')
});
export default function CreatePalindrome() {
return <Box>Lorem ipsum</Box>;
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
// import image from '@assets/text.png';
export const tool = defineTool('string', {
name: 'Create palindrome',
path: 'create-palindrome',
// image,
description: '',
shortDescription: '',
keywords: ['create', 'palindrome'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,36 @@
import { reverseString } from 'utils/string';
export function createPalindrome(
input: string,
lastChar: boolean // only checkbox is need here to handle it [instead of two combo boxes]
) {
if (!input) return '';
let result: string;
let reversedString: string;
// reverse the whole input if lastChar enabled
reversedString = lastChar
? reverseString(input)
: reverseString(input.slice(0, -1));
result = input.concat(reversedString);
return result;
}
export function createPalindromeList(
input: string,
lastChar: boolean,
multiLine: boolean
): string {
if (!input) return '';
let array: string[];
const result: string[] = [];
if (!multiLine) return createPalindrome(input, lastChar);
else {
array = input.split('\n');
for (const word of array) {
result.push(createPalindrome(word, lastChar));
}
}
return result.join('\n');
}

View File

@@ -0,0 +1,61 @@
import { describe, expect, it } from 'vitest';
import { extractSubstring } from './service';
describe('extractSubstring', () => {
it('should extract a substring from single-line input', () => {
const input = 'hello world';
const result = extractSubstring(input, 1, 4, false, false);
expect(result).toBe('hell');
});
it('should extract and reverse a substring from single-line input', () => {
const input = 'hello world';
const result = extractSubstring(input, 1, 5, false, true);
expect(result).toBe('olleh');
});
it('should extract substrings from multi-line input', () => {
const input = 'hello\nworld';
const result = extractSubstring(input, 1, 5, true, false);
expect(result).toBe('hello\nworld');
});
it('should extract and reverse substrings from multi-line input', () => {
const input = 'hello\nworld';
const result = extractSubstring(input, 1, 4, true, true);
expect(result).toBe('lleh\nlrow');
});
it('should handle empty input', () => {
const input = '';
const result = extractSubstring(input, 1, 5, false, false);
expect(result).toBe('');
});
it('should handle start and length out of bounds', () => {
const input = 'hello';
const result = extractSubstring(input, 10, 5, false, false);
expect(result).toBe('');
});
it('should handle negative start and length', () => {
expect(() => extractSubstring('hello', -1, 5, false, false)).toThrow(
'Start index must be greater than zero.'
);
expect(() => extractSubstring('hello', 1, -5, false, false)).toThrow(
'Length value must be greater than or equal to zero.'
);
});
it('should handle zero length', () => {
const input = 'hello';
const result = extractSubstring(input, 1, 0, false, false);
expect(result).toBe('');
});
it('should work', () => {
const input = 'je me nomme king\n22 est mon chiffre';
const result = extractSubstring(input, 12, 7, true, false);
expect(result).toBe(' king\nchiffre');
});
});

View File

@@ -0,0 +1,11 @@
import { Box } from '@mui/material';
import React from 'react';
import * as Yup from 'yup';
const initialValues = {};
const validationSchema = Yup.object({
// splitSeparator: Yup.string().required('The separator is required')
});
export default function ExtractSubstring() {
return <Box>Lorem ipsum</Box>;
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
// import image from '@assets/text.png';
export const tool = defineTool('string', {
name: 'Extract substring',
path: 'extract-substring',
// image,
description: '',
shortDescription: '',
keywords: ['extract', 'substring'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,36 @@
import { reverseString } from 'utils/string';
export function extractSubstring(
input: string,
start: number,
length: number,
multiLine: boolean,
reverse: boolean
): string {
if (!input) return '';
// edge Cases
if (start <= 0) throw new Error('Start index must be greater than zero.');
if (length < 0)
throw new Error('Length value must be greater than or equal to zero.');
if (length === 0) return '';
let array: string[];
let result: string[] = [];
const extract = (str: string, start: number, length: number): string => {
const end = start - 1 + length;
if (start - 1 >= str.length) return '';
return str.substring(start - 1, Math.min(end, str.length));
};
if (!multiLine) {
result.push(extract(input, start, length));
} else {
array = input.split('\n');
for (const word of array) {
result.push(extract(word, start, length));
}
}
result = reverse ? result.map((word) => reverseString(word)) : result;
return result.join('\n');
}

View File

@@ -0,0 +1,21 @@
import { tool as stringReverse } from './reverse/meta';
import { tool as stringRandomizeCase } from './randomize-case/meta';
import { tool as stringUppercase } from './uppercase/meta';
import { tool as stringExtractSubstring } from './extract-substring/meta';
import { tool as stringCreatePalindrome } from './create-palindrome/meta';
import { tool as stringPalindrome } from './palindrome/meta';
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,
stringToMorse,
stringReverse,
stringRandomizeCase,
stringUppercase,
stringExtractSubstring,
stringCreatePalindrome,
stringPalindrome
];

View File

@@ -0,0 +1,190 @@
import { Box } from '@mui/material';
import React, { useState } from 'react';
import * as Yup from 'yup';
import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult';
import ToolOptions from '@components/options/ToolOptions';
import { mergeText } from './service';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
import ToolInputAndResult from '@components/ToolInputAndResult';
import ToolInfo from '@components/ToolInfo';
import Separator from '@components/Separator';
import Examples from '@components/examples/Examples';
const initialValues = {
joinCharacter: '',
deleteBlank: true,
deleteTrailing: true
};
const validationSchema = Yup.object().shape({
joinCharacter: Yup.string().required('Join character is required'),
deleteBlank: Yup.boolean().required('Delete blank is required'),
deleteTrailing: Yup.boolean().required('Delete trailing is required')
});
const mergeOptions = {
placeholder: 'Join Character',
description:
'Symbol that connects broken\n' + 'pieces of text. (Space by default.)\n',
accessor: 'joinCharacter' as keyof typeof initialValues
};
const blankTrailingOptions: {
title: string;
description: string;
accessor: keyof typeof initialValues;
}[] = [
{
title: 'Delete Blank Lines',
description: "Delete lines that don't have\n text symbols.\n",
accessor: 'deleteBlank'
},
{
title: 'Delete Trailing Spaces',
description: 'Remove spaces and tabs at\n the end of the lines.\n',
accessor: 'deleteTrailing'
}
];
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 [result, setResult] = useState<string>('');
const compute = (optionsValues: typeof initialValues, input: any) => {
const { joinCharacter, deleteBlank, deleteTrailing } = optionsValues;
setResult(mergeText(input, deleteBlank, deleteTrailing, joinCharacter));
};
function changeInputResult(input: string, result: string) {
setInput(input);
setResult(result);
const toolsElement = document.getElementById('tool');
if (toolsElement) {
toolsElement.scrollIntoView({ behavior: 'smooth' });
}
}
return (
<Box>
<ToolInputAndResult
input={
<ToolTextInput
title={'Text Pieces'}
value={input}
onChange={setInput}
/>
}
result={<ToolTextResult title={'Joined Text'} value={result} />}
/>
<ToolOptions
compute={compute}
getGroups={({ values, updateField }) => [
{
title: 'Text Merged Options',
component: (
<TextFieldWithDesc
placeholder={mergeOptions.placeholder}
value={values['joinCharacter']}
onOwnChange={(value) =>
updateField(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) => updateField(option.accessor, value)}
description={option.description}
/>
))
}
]}
initialValues={initialValues}
input={input}
/>
<ToolInfo
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>
);
}

View File

@@ -0,0 +1,14 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
import image from '@assets/text.png';
export const tool = defineTool('string', {
path: 'join',
name: 'Text Joiner',
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'))
});

View File

@@ -0,0 +1,17 @@
export function mergeText(
text: string,
deleteBlankLines: boolean = true,
deleteTrailingSpaces: boolean = true,
joinCharacter: string = ''
): string {
let processedLines: string[] = text.split('\n');
if (deleteTrailingSpaces) {
processedLines = processedLines.map((line) => line.trimEnd());
}
if (deleteBlankLines) {
processedLines = processedLines.filter((line) => line.trim());
}
return processedLines.join(joinCharacter);
}

View File

@@ -0,0 +1,18 @@
import { expect, test } from '@playwright/test';
test.describe('JoinText Component', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/string/join');
});
test('should merge text pieces with specified join character', async ({
page
}) => {
// Input the text pieces
await page.getByTestId('text-input').fill('1\n2');
const result = await page.getByTestId('text-result').inputValue();
expect(result).toBe('12');
});
});

View File

@@ -0,0 +1,64 @@
import { describe, expect, it } from 'vitest';
import { mergeText } from './service';
describe('mergeText', () => {
it('should merge lines with default settings (delete blank lines, delete trailing spaces, join with empty string)', () => {
const input = 'line1 \n \nline2\nline3 \n\nline4';
const expected = 'line1line2line3line4';
expect(mergeText(input)).toBe(expected);
});
it('should merge lines and preserve blank lines when deleteBlankLines is false', () => {
const input = 'line1 \n \nline2\nline3 \n\nline4';
const expected = 'line1line2line3line4';
expect(mergeText(input, false, true, '')).toBe(expected);
});
it('should merge lines and preserve trailing spaces when deleteTrailingSpaces is false', () => {
const input = 'line1 \n \nline2\nline3 \n\nline4';
const expected = 'line1 line2line3 line4';
expect(mergeText(input, true, false)).toBe(expected);
});
it('should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false', () => {
const input = 'line1 \n \nline2\nline3 \n\nline4';
const expected = 'line1 line2line3 line4';
expect(mergeText(input, false, false)).toBe(expected);
});
it('should merge lines with a specified joinCharacter', () => {
const input = 'line1 \n \nline2\nline3 \n\nline4';
const expected = 'line1 line2 line3 line4';
expect(mergeText(input, true, true, ' ')).toBe(expected);
});
it('should handle empty input', () => {
const input = '';
const expected = '';
expect(mergeText(input)).toBe(expected);
});
it('should handle input with only blank lines', () => {
const input = ' \n \n\n';
const expected = '';
expect(mergeText(input)).toBe(expected);
});
it('should handle input with only trailing spaces', () => {
const input = 'line1 \nline2 \nline3 ';
const expected = 'line1line2line3';
expect(mergeText(input)).toBe(expected);
});
it('should handle single line input', () => {
const input = 'single line';
const expected = 'single line';
expect(mergeText(input)).toBe(expected);
});
it('should join lines with new line character when joinCharacter is set to "\\n"', () => {
const input = 'line1 \n \nline2\nline3 \n\nline4';
const expected = 'line1\nline2\nline3\nline4';
expect(mergeText(input, true, true, '\n')).toBe(expected);
});
});

View File

@@ -0,0 +1,11 @@
import { Box } from '@mui/material';
import React from 'react';
import * as Yup from 'yup';
const initialValues = {};
const validationSchema = Yup.object({
// splitSeparator: Yup.string().required('The separator is required')
});
export default function Palindrome() {
return <Box>Lorem ipsum</Box>;
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
// import image from '@assets/text.png';
export const tool = defineTool('string', {
name: 'Palindrome',
path: 'palindrome',
// image,
description: '',
shortDescription: '',
keywords: ['palindrome'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,60 @@
import { describe, expect } from 'vitest';
import { palindromeList } from './service';
describe('palindromeList', () => {
test('should return true for single character words', () => {
const input = 'a|b|c';
const separator = '|';
const result = palindromeList('symbol', input, separator);
expect(result).toBe('true|true|true');
});
test('should return false for non-palindromes', () => {
const input = 'hello|world';
const separator = '|';
const result = palindromeList('symbol', input, separator);
expect(result).toBe('false|false');
});
test('should split using regex', () => {
const input = 'racecar,abba,hello';
const separator = ',';
const result = palindromeList('regex', input, separator);
expect(result).toBe('true,true,false');
});
test('should return empty string for empty input', () => {
const input = '';
const separator = '|';
const result = palindromeList('symbol', input, separator);
expect(result).toBe('');
});
test('should split using custom separator', () => {
const input = 'racecar;abba;hello';
const separator = ';';
const result = palindromeList('symbol', input, separator);
expect(result).toBe('true;true;false');
});
test('should handle leading and trailing spaces', () => {
const input = ' racecar | abba | hello ';
const separator = '|';
const result = palindromeList('symbol', input, separator);
expect(result).toBe('true|true|false');
});
test('should handle multilines checking with trimming', () => {
const input = ' racecar \n abba \n hello ';
const separator = '\n';
const result = palindromeList('symbol', input, separator);
expect(result).toBe('true\ntrue\nfalse');
});
test('should handle empty strings in input', () => {
const input = 'racecar||hello';
const separator = '|';
const result = palindromeList('symbol', input, separator);
expect(result).toBe('true|true|false');
});
});

View File

@@ -0,0 +1,41 @@
export type SplitOperatorType = 'symbol' | 'regex';
function isPalindrome(word: string, left: number, right: number): boolean {
if (left >= right) return true;
if (word[left] !== word[right]) return false;
return isPalindrome(word, left + 1, right - 1);
}
// check each word of the input and add the palindrome status in an array
function checkPalindromes(array: string[]): boolean[] {
const status: boolean[] = [];
for (const word of array) {
const palindromeStatus = isPalindrome(word, 0, word.length - 1);
status.push(palindromeStatus);
}
return status;
}
export function palindromeList(
splitOperatorType: SplitOperatorType,
input: string,
separator: string // the splitting separator will be the joining separator for visual satisfaction
): string {
if (!input) return '';
let array: string[];
switch (splitOperatorType) {
case 'symbol':
array = input.split(separator);
break;
case 'regex':
array = input.split(new RegExp(separator));
break;
}
// trim all items to focus on the word and not biasing the result due to spaces (leading and trailing)
array = array.map((item) => item.trim());
const statusArray = checkPalindromes(array);
return statusArray.map((status) => status.toString()).join(separator);
}

View File

@@ -0,0 +1,11 @@
import { Box } from '@mui/material';
import React from 'react';
import * as Yup from 'yup';
const initialValues = {};
const validationSchema = Yup.object({
// splitSeparator: Yup.string().required('The separator is required')
});
export default function RandomizeCase() {
return <Box>Lorem ipsum</Box>;
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
// import image from '@assets/text.png';
export const tool = defineTool('string', {
name: 'Randomize case',
path: 'randomize-case',
// image,
description: '',
shortDescription: '',
keywords: ['randomize', 'case'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,50 @@
import { describe, expect, it } from 'vitest';
import { randomizeCase } from './service';
describe('randomizeCase', () => {
it('should randomize the case of each character in the string', () => {
const input = 'hello world';
const result = randomizeCase(input);
// Ensure the output length is the same
expect(result).toHaveLength(input.length);
// Ensure each character in the input string appears in the result
for (let i = 0; i < input.length; i++) {
const inputChar = input[i];
const resultChar = result[i];
if (/[a-zA-Z]/.test(inputChar)) {
expect([inputChar.toLowerCase(), inputChar.toUpperCase()]).toContain(
resultChar
);
} else {
expect(inputChar).toBe(resultChar);
}
}
});
it('should handle an empty string', () => {
const input = '';
const result = randomizeCase(input);
expect(result).toBe('');
});
it('should handle a string with numbers and symbols', () => {
const input = '123 hello! @world';
const result = randomizeCase(input);
// Ensure the output length is the same
expect(result).toHaveLength(input.length);
// Ensure numbers and symbols remain unchanged
for (let i = 0; i < input.length; i++) {
const inputChar = input[i];
const resultChar = result[i];
if (!/[a-zA-Z]/.test(inputChar)) {
expect(inputChar).toBe(resultChar);
}
}
});
});

View File

@@ -0,0 +1,8 @@
export function randomizeCase(input: string): string {
return input
.split('')
.map((char) =>
Math.random() < 0.5 ? char.toLowerCase() : char.toUpperCase()
)
.join('');
}

View File

@@ -0,0 +1,11 @@
import { Box } from '@mui/material';
import React from 'react';
import * as Yup from 'yup';
const initialValues = {};
const validationSchema = Yup.object({
// splitSeparator: Yup.string().required('The separator is required')
});
export default function Reverse() {
return <Box>Lorem ipsum</Box>;
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
// import image from '@assets/text.png';
export const tool = defineTool('string', {
name: 'Reverse',
path: 'reverse',
// image,
description: '',
shortDescription: '',
keywords: ['reverse'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,52 @@
import { describe, expect, it } from 'vitest';
import { stringReverser } from './service';
describe('stringReverser', () => {
it('should reverse a single-line string', () => {
const input = 'hello world';
const result = stringReverser(input, false, false, false);
expect(result).toBe('dlrow olleh');
});
it('should reverse each line in a multi-line string', () => {
const input = 'hello\nworld';
const result = stringReverser(input, true, false, false);
expect(result).toBe('olleh\ndlrow');
});
it('should remove empty items if emptyItems is true', () => {
const input = 'hello\n\nworld';
const result = stringReverser(input, true, true, false);
expect(result).toBe('olleh\ndlrow');
});
it('should trim each line if trim is true', () => {
const input = ' hello \n world ';
const result = stringReverser(input, true, false, true);
expect(result).toBe('olleh\ndlrow');
});
it('should handle empty input', () => {
const input = '';
const result = stringReverser(input, false, false, false);
expect(result).toBe('');
});
it('should handle a single line with emptyItems and trim', () => {
const input = ' hello world ';
const result = stringReverser(input, false, true, true);
expect(result).toBe('dlrow olleh');
});
it('should handle a single line with emptyItems and non trim', () => {
const input = ' hello world ';
const result = stringReverser(input, false, true, false);
expect(result).toBe(' dlrow olleh ');
});
it('should handle a multi line with emptyItems and non trim', () => {
const input = ' hello\n\n\n\nworld ';
const result = stringReverser(input, true, true, false);
expect(result).toBe('olleh \n dlrow');
});
});

View File

@@ -0,0 +1,30 @@
import { reverseString } from 'utils/string';
export function stringReverser(
input: string,
multiLine: boolean,
emptyItems: boolean,
trim: boolean
) {
let array: string[] = [];
let result: string[] = [];
// split the input in multiLine mode
if (multiLine) {
array = input.split('\n');
} else {
array.push(input);
}
// handle empty items
if (emptyItems) {
array = array.filter(Boolean);
}
// Handle trim
if (trim) {
array = array.map((line) => line.trim());
}
result = array.map((element) => reverseString(element));
return result.join('\n');
}

View File

@@ -0,0 +1,148 @@
import { Box } from '@mui/material';
import React, { useState } from 'react';
import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult';
import ToolOptions from '@components/options/ToolOptions';
import { compute, SplitOperatorType } from './service';
import RadioWithTextField from '@components/options/RadioWithTextField';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import ToolInputAndResult from '@components/ToolInputAndResult';
const initialValues = {
splitSeparatorType: 'symbol' as SplitOperatorType,
symbolValue: ' ',
regexValue: '/\\s+/',
lengthValue: '16',
chunksValue: '4',
outputSeparator: '\\n',
charBeforeChunk: '',
charAfterChunk: ''
};
const splitOperators: {
title: string;
description: string;
type: SplitOperatorType;
}[] = [
{
title: 'Use a Symbol for Splitting',
description:
'Character that will be used to\n' +
'break text into parts.\n' +
'(Space by default.)',
type: 'symbol'
},
{
title: 'Use a Regex for Splitting',
type: 'regex',
description:
'Regular expression that will be\n' +
'used to break text into parts.\n' +
'(Multiple spaces by default.)'
},
{
title: 'Use Length for Splitting',
description:
'Number of symbols that will be\n' + 'put in each output chunk.',
type: 'length'
},
{
title: 'Use a Number of Chunks',
description: 'Number of chunks of equal\n' + 'length in the output.',
type: 'chunks'
}
];
const outputOptions: {
description: string;
accessor: keyof typeof initialValues;
}[] = [
{
description:
'Character that will be put\n' +
'between the split chunks.\n' +
'(It\'s newline "\\n" by default.)',
accessor: 'outputSeparator'
},
{
description: 'Character before each chunk',
accessor: 'charBeforeChunk'
},
{
description: 'Character after each chunk',
accessor: 'charAfterChunk'
}
];
export default function SplitText() {
const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>('');
// const formRef = useRef<FormikProps<typeof initialValues>>(null);
const computeExternal = (optionsValues: typeof initialValues, input: any) => {
const {
splitSeparatorType,
outputSeparator,
charBeforeChunk,
charAfterChunk,
chunksValue,
symbolValue,
regexValue,
lengthValue
} = optionsValues;
setResult(
compute(
splitSeparatorType,
input,
symbolValue,
regexValue,
Number(lengthValue),
Number(chunksValue),
charBeforeChunk,
charAfterChunk,
outputSeparator
)
);
};
return (
<Box>
<ToolInputAndResult
input={<ToolTextInput value={input} onChange={setInput} />}
result={<ToolTextResult title={'Text pieces'} value={result} />}
/>
<ToolOptions
compute={computeExternal}
getGroups={({ values, updateField }) => [
{
title: 'Split separator options',
component: splitOperators.map(({ title, description, type }) => (
<RadioWithTextField
key={type}
checked={type === values.splitSeparatorType}
title={title}
fieldName={'splitSeparatorType'}
description={description}
value={values[`${type}Value`]}
onRadioClick={() => updateField('splitSeparatorType', type)}
onTextChange={(val) => updateField(`${type}Value`, val)}
/>
))
},
{
title: 'Output separator options',
component: outputOptions.map((option) => (
<TextFieldWithDesc
key={option.accessor}
value={values[option.accessor]}
onOwnChange={(value) => updateField(option.accessor, value)}
description={option.description}
/>
))
}
]}
initialValues={initialValues}
input={input}
/>
</Box>
);
}

View File

@@ -0,0 +1,14 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
import image from '@assets/text.png';
export const tool = defineTool('string', {
path: 'split',
name: 'Text splitter',
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'))
});

View File

@@ -0,0 +1,65 @@
export type SplitOperatorType = 'symbol' | 'regex' | 'length' | 'chunks';
function splitTextByLength(text: string, length: number) {
if (length <= 0) throw new Error('Length must be a positive number');
const result: string[] = [];
for (let i = 0; i < text.length; i += length) {
result.push(text.slice(i, i + length));
}
return result;
}
function splitIntoChunks(text: string, numChunks: number) {
if (numChunks <= 0)
throw new Error('Number of chunks must be a positive number');
const totalLength = text.length;
if (totalLength < numChunks)
throw new Error(
'Text length must be at least as long as the number of chunks'
);
const chunkSize = Math.ceil(totalLength / numChunks); // Calculate the chunk size, rounding up to handle remainders
let result = [];
for (let i = 0; i < totalLength; i += chunkSize) {
result.push(text.slice(i, i + chunkSize));
}
// Ensure the result contains exactly numChunks, adjusting the last chunk if necessary
if (result.length > numChunks) {
result[numChunks - 1] = result.slice(numChunks - 1).join(''); // Merge any extra chunks into the last chunk
result = result.slice(0, numChunks); // Take only the first numChunks chunks
}
return result;
}
export function compute(
splitSeparatorType: SplitOperatorType,
input: string,
symbolValue: string,
regexValue: string,
lengthValue: number,
chunksValue: number,
charBeforeChunk: string,
charAfterChunk: string,
outputSeparator: string
) {
let splitText;
switch (splitSeparatorType) {
case 'symbol':
splitText = input.split(symbolValue);
break;
case 'regex':
splitText = input.split(new RegExp(regexValue));
break;
case 'length':
splitText = splitTextByLength(input, lengthValue);
break;
case 'chunks':
splitText = splitIntoChunks(input, chunksValue).map(
(chunk) => `${charBeforeChunk}${chunk}${charAfterChunk}`
);
}
return splitText.join(outputSeparator);
}

View File

@@ -0,0 +1,72 @@
import { describe, expect, it } from 'vitest';
import { compute } from './service';
describe('compute function', () => {
it('should split by symbol', () => {
const result = compute('symbol', 'hello world', ' ', '', 0, 0, '', '', ',');
expect(result).toBe('hello,world');
});
it('should split by regex', () => {
const result = compute(
'regex',
'hello1world2again',
'',
'\\d',
0,
0,
'',
'',
','
);
expect(result).toBe('hello,world,again');
});
it('should split by length', () => {
const result = compute('length', 'helloworld', '', '', 3, 0, '', '', ',');
expect(result).toBe('hel,low,orl,d');
});
it('should split into chunks', () => {
const result = compute(
'chunks',
'helloworldagain',
'',
'',
0,
3,
'[',
']',
','
);
expect(result).toBe('[hello],[world],[again]');
});
it('should handle empty input', () => {
const result = compute('symbol', '', ' ', '', 0, 0, '', '', ',');
expect(result).toBe('');
});
it('should handle length greater than text length', () => {
const result = compute('length', 'hi', '', '', 5, 0, '', '', ',');
expect(result).toBe('hi');
});
it('should handle chunks greater than text length', () => {
expect(() => {
compute('chunks', 'hi', '', '', 0, 5, '', '', ',');
}).toThrow('Text length must be at least as long as the number of chunks');
});
it('should handle invalid length', () => {
expect(() => {
compute('length', 'hello', '', '', -1, 0, '', '', ',');
}).toThrow('Length must be a positive number');
});
it('should handle invalid chunks', () => {
expect(() => {
compute('chunks', 'hello', '', '', 0, 0, '', '', ',');
}).toThrow('Number of chunks must be a positive number');
});
});

View File

@@ -0,0 +1,63 @@
import { Box } from '@mui/material';
import React, { useState } from 'react';
import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult';
import ToolOptions from '@components/options/ToolOptions';
import { compute } from './service';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import ToolInputAndResult from '@components/ToolInputAndResult';
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 computeOptions = (optionsValues: typeof initialValues, input: any) => {
const { dotSymbol, dashSymbol } = optionsValues;
setResult(compute(input, dotSymbol, dashSymbol));
};
return (
<Box>
<ToolInputAndResult
input={<ToolTextInput value={input} onChange={setInput} />}
result={<ToolTextResult title={'Morse code'} value={result} />}
/>
<ToolOptions
compute={computeOptions}
getGroups={({ values, updateField }) => [
{
title: 'Short Signal',
component: (
<TextFieldWithDesc
description={
'Symbol that will correspond to the dot in Morse code.'
}
value={values.dotSymbol}
onOwnChange={(val) => updateField('dotSymbol', val)}
/>
)
},
{
title: 'Long Signal',
component: (
<TextFieldWithDesc
description={
'Symbol that will correspond to the dash in Morse code.'
}
value={values.dashSymbol}
onOwnChange={(val) => updateField('dashSymbol', val)}
/>
)
}
]}
initialValues={initialValues}
input={input}
/>
</Box>
);
}

View File

@@ -0,0 +1,14 @@
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:
"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'))
});

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

View File

@@ -0,0 +1,50 @@
import { describe, expect, it } 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);
});
});

View File

@@ -0,0 +1,11 @@
import { Box } from '@mui/material';
import React from 'react';
import * as Yup from 'yup';
const initialValues = {};
const validationSchema = Yup.object({
// splitSeparator: Yup.string().required('The separator is required')
});
export default function Uppercase() {
return <Box>Lorem ipsum</Box>;
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
// import image from '@assets/text.png';
export const tool = defineTool('string', {
name: 'Uppercase',
path: 'uppercase',
// image,
description: '',
shortDescription: '',
keywords: ['uppercase'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,3 @@
export function UppercaseInput(input: string): string {
return input.toUpperCase();
}

View File

@@ -0,0 +1,34 @@
import { describe, expect, it } from 'vitest';
import { UppercaseInput } from './service';
describe('UppercaseInput', () => {
it('should convert a lowercase string to uppercase', () => {
const input = 'hello';
const result = UppercaseInput(input);
expect(result).toBe('HELLO');
});
it('should convert a mixed case string to uppercase', () => {
const input = 'HeLLo WoRLd';
const result = UppercaseInput(input);
expect(result).toBe('HELLO WORLD');
});
it('should convert an already uppercase string to uppercase', () => {
const input = 'HELLO';
const result = UppercaseInput(input);
expect(result).toBe('HELLO');
});
it('should handle an empty string', () => {
const input = '';
const result = UppercaseInput(input);
expect(result).toBe('');
});
it('should handle a string with numbers and symbols', () => {
const input = '123 hello! @world';
const result = UppercaseInput(input);
expect(result).toBe('123 HELLO! @WORLD');
});
});