diff --git a/.eslintrc b/.eslintrc
index a473206..e857efc 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -43,6 +43,7 @@
"tailwindcss/no-custom-classname": "warn",
"tailwindcss/no-contradicting-classname": "error",
"@typescript-eslint/ban-types": "off",
- "@typescript-eslint/ban-ts-comment": "off"
+ "@typescript-eslint/ban-ts-comment": "off",
+ "@typescript-eslint/no-explicit-any": "off"
}
}
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 3400181..4a6ed24 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,20 +4,24 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
+
+
+
+
-
-
+
+
@@ -187,15 +191,7 @@
-
-
-
-
- 1718816900959
-
-
-
- 1718816900959
+
@@ -581,7 +577,15 @@
1719275214988
-
+
+
+ 1719277679968
+
+
+
+ 1719277679969
+
+
@@ -602,7 +606,6 @@
-
@@ -627,7 +630,8 @@
-
+
+
diff --git a/scripts/create-tool.mjs b/scripts/create-tool.mjs
index a6f5c12..a8c1715 100644
--- a/scripts/create-tool.mjs
+++ b/scripts/create-tool.mjs
@@ -30,7 +30,7 @@ function createFolderStructure(basePath, foldersToCreateIndexCount) {
}
const indexPath = join(currentPath, 'index.ts')
if (!fs.existsSync(indexPath) && index < folderArray.length - 1 && index >= folderArray.length - 1 - foldersToCreateIndexCount) {
- fs.writeFileSync(indexPath, '// index.ts file')
+ fs.writeFileSync(indexPath, `export const ${currentPath.split(sep)[currentPath.split(sep).length - 1]}Tools = [];\n`)
console.log(`File created: ${indexPath}`)
}
// Recursively create the next folder
diff --git a/src/components/options/ColorSelector.tsx b/src/components/options/ColorSelector.tsx
index 64a4f89..0a6cb1e 100644
--- a/src/components/options/ColorSelector.tsx
+++ b/src/components/options/ColorSelector.tsx
@@ -3,7 +3,7 @@ import { Box, Stack, TextField } from '@mui/material';
import PaletteIcon from '@mui/icons-material/Palette';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
-import { descriptionFontSize } from '../../config/uiConfig';
+import { globalDescriptionFontSize } from '../../config/uiConfig';
interface ColorSelectorProps {
value: string;
@@ -44,7 +44,9 @@ const ColorSelector: React.FC = ({
onChange={handleColorChange}
/>
- {description}
+
+ {description}
+
);
};
diff --git a/src/components/options/RadioWithTextField.tsx b/src/components/options/RadioWithTextField.tsx
index d82ca0c..75ce672 100644
--- a/src/components/options/RadioWithTextField.tsx
+++ b/src/components/options/RadioWithTextField.tsx
@@ -4,37 +4,38 @@ import { Field } from 'formik';
import Typography from '@mui/material/Typography';
import React from 'react';
import TextFieldWithDesc from './TextFieldWithDesc';
+import { globalDescriptionFontSize } from '../../config/uiConfig';
+import SimpleRadio from './SimpleRadio';
const RadioWithTextField = ({
fieldName,
- type,
+ radioValue,
title,
- onTypeChange,
+ onRadioChange,
value,
description,
- onTextChange
+ onTextChange,
+ typeDescription
}: {
fieldName: string;
title: string;
- type: T;
- onTypeChange: (val: T) => void;
+ radioValue: T;
+ onRadioChange: (val: T) => void;
value: string;
description: string;
onTextChange: (value: string) => void;
+ typeDescription?: string;
}) => {
- const onChange = () => onTypeChange(type);
+ const onChange = () => onRadioChange(radioValue);
return (
-
-
- {title}
-
+
void;
+ fieldName: string;
+ value: any;
+ title: string;
+ description?: string;
+}
+
+export default function SimpleRadio({
+ onChange,
+ fieldName,
+ value,
+ title,
+ description
+}: SimpleRadioProps) {
+ return (
+
+
+
+ {title}
+
+ {description && (
+
+ {description}
+
+ )}
+
+ );
+}
diff --git a/src/components/options/ToolOptionGroups.tsx b/src/components/options/ToolOptionGroups.tsx
index ed7f465..a2abd8a 100644
--- a/src/components/options/ToolOptionGroups.tsx
+++ b/src/components/options/ToolOptionGroups.tsx
@@ -11,7 +11,9 @@ export default function ToolOptionGroups({
{groups.map((group) => (
- {group.title}
+
+ {group.title}
+
{group.component}
))}
diff --git a/src/config/uiConfig.ts b/src/config/uiConfig.ts
index 5ec7d7b..a9455c0 100644
--- a/src/config/uiConfig.ts
+++ b/src/config/uiConfig.ts
@@ -1,2 +1,2 @@
export const globalInputHeight = 300;
-export const descriptionFontSize = 12;
+export const globalDescriptionFontSize = 12;
diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx
index beb5f81..9abf18c 100644
--- a/src/pages/home/index.tsx
+++ b/src/pages/home/index.tsx
@@ -26,7 +26,7 @@ const exampleTools: { label: string; url: string }[] = [
{ label: 'Find and replace text', url: '' },
{ label: 'Convert emoji to image', url: '' },
{ label: 'Split a string', url: '/string/split' },
- { label: 'Calculate number sum', url: '' },
+ { label: 'Calculate number sum', url: '/number/sum' },
{ label: 'Pixelate an image', url: '' }
];
export default function Home() {
diff --git a/src/pages/number/index.ts b/src/pages/number/index.ts
new file mode 100644
index 0000000..5bead17
--- /dev/null
+++ b/src/pages/number/index.ts
@@ -0,0 +1,3 @@
+import { tool as numberSum } from './sum/meta';
+
+export const numberTools = [numberSum];
diff --git a/src/pages/number/sum/index.tsx b/src/pages/number/sum/index.tsx
new file mode 100644
index 0000000..ff5ef96
--- /dev/null
+++ b/src/pages/number/sum/index.tsx
@@ -0,0 +1,157 @@
+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, NumberExtractionType } from './service';
+import { CustomSnackBarContext } from '../../../contexts/CustomSnackBarContext';
+import RadioWithTextField from '../../../components/options/RadioWithTextField';
+import ToolOptionGroups from '../../../components/options/ToolOptionGroups';
+import SimpleRadio from '../../../components/options/SimpleRadio';
+import CheckboxWithDesc from '../../../components/options/CheckboxWithDesc';
+
+const initialValues = {
+ extractionType: 'smart' as NumberExtractionType,
+ separator: '\\n',
+ printRunningSum: false
+};
+const extractionTypes: {
+ title: string;
+ description: string;
+ type: NumberExtractionType;
+ withTextField: boolean;
+ textValueAccessor?: keyof typeof initialValues;
+}[] = [
+ {
+ title: 'Smart sum',
+ description: 'Auto detect numbers in the input.',
+ type: 'smart',
+ withTextField: false
+ },
+ {
+ title: 'Number Delimiter',
+ type: 'delimiter',
+ description:
+ 'Input SeparatorCustomize the number separator here. (By default a line break.)',
+ withTextField: true,
+ textValueAccessor: 'separator'
+ }
+];
+
+export default function SplitText() {
+ const [input, setInput] = useState('');
+ const [result, setResult] = useState('');
+ // const formRef = useRef>(null);
+ const { showSnackBar } = useContext(CustomSnackBarContext);
+
+ const FormikListenerComponent = () => {
+ const { values } = useFormikContext();
+
+ useEffect(() => {
+ try {
+ const { extractionType, printRunningSum, separator } = values;
+
+ setResult(compute(input, extractionType, printRunningSum, separator));
+ } 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 }) => (
+
+
+
+ withTextField ? (
+
+ setFieldValue('extractionType', type)
+ }
+ onTextChange={(val) =>
+ setFieldValue(textValueAccessor ?? '', val)
+ }
+ />
+ ) : (
+
+ setFieldValue('extractionType', type)
+ }
+ fieldName={'extractionType'}
+ value={values.extractionType}
+ description={description}
+ title={title}
+ />
+ )
+ )
+ },
+ {
+ title: 'Running Sum',
+ component: (
+
+ setFieldValue('printRunningSum', value)
+ }
+ />
+ )
+ }
+ ]}
+ />
+
+ )}
+
+
+
+ );
+}
diff --git a/src/pages/number/sum/meta.ts b/src/pages/number/sum/meta.ts
new file mode 100644
index 0000000..ffc8520
--- /dev/null
+++ b/src/pages/number/sum/meta.ts
@@ -0,0 +1,13 @@
+import { defineTool } from '@tools/defineTool';
+import { lazy } from 'react';
+// import image from '@assets/text.png';
+
+export const tool = defineTool('number', {
+ name: 'Number Sum Calculator',
+ path: 'sum',
+ // image,
+ description:
+ 'Quickly calculate the sum of numbers in your browser. To get your sum, just enter your list of numbers in the input field, adjust the separator between the numbers in the options below, and this utility will add up all these numbers.',
+ keywords: ['sum'],
+ component: lazy(() => import('./index'))
+});
diff --git a/src/pages/number/sum/service.ts b/src/pages/number/sum/service.ts
new file mode 100644
index 0000000..4a013b5
--- /dev/null
+++ b/src/pages/number/sum/service.ts
@@ -0,0 +1,29 @@
+export type NumberExtractionType = 'smart' | 'delimiter';
+
+function getAllNumbers(text: string): number[] {
+ const regex = /\d+/g;
+ const matches = text.match(regex);
+ return matches ? matches.map(Number) : [];
+}
+
+export const compute = (
+ input: string,
+ extractionType: NumberExtractionType,
+ printRunningSum: boolean,
+ separator: string
+) => {
+ let numbers: number[] = [];
+ if (extractionType === 'smart') {
+ numbers = getAllNumbers(input);
+ } else {
+ const parts = input.split(separator);
+ // Filter out and convert parts that are numbers
+ numbers = parts
+ .filter((part) => !isNaN(Number(part)) && part.trim() !== '')
+ .map(Number);
+ }
+ return numbers.reduce(
+ (previousValue, currentValue) => previousValue + currentValue,
+ 0
+ );
+};
diff --git a/src/pages/number/sum/sum.service.test.ts b/src/pages/number/sum/sum.service.test.ts
new file mode 100644
index 0000000..c18748e
--- /dev/null
+++ b/src/pages/number/sum/sum.service.test.ts
@@ -0,0 +1,6 @@
+import { expect, describe, it } from 'vitest';
+// import { } from './service';
+//
+// describe('sum', () => {
+//
+// })
\ No newline at end of file
diff --git a/src/pages/string/split/index.tsx b/src/pages/string/split/index.tsx
index 8664495..8f7ccfd 100644
--- a/src/pages/string/split/index.tsx
+++ b/src/pages/string/split/index.tsx
@@ -151,12 +151,12 @@ export default function SplitText() {
({ title, description, type }) => (
+ onRadioChange={(type) =>
setFieldValue('splitSeparatorType', type)
}
onTextChange={(val) =>
diff --git a/src/pages/string/to-morse/meta.ts b/src/pages/string/to-morse/meta.ts
index cc7c51e..80b2b4d 100644
--- a/src/pages/string/to-morse/meta.ts
+++ b/src/pages/string/to-morse/meta.ts
@@ -6,7 +6,8 @@ export const tool = defineTool('string', {
name: 'String To morse',
path: 'to-morse',
// image,
- description: '',
+ 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.",
keywords: ['to', 'morse'],
component: lazy(() => import('./index'))
});
diff --git a/src/tools/index.ts b/src/tools/index.ts
index 2f5e19b..f460aa9 100644
--- a/src/tools/index.ts
+++ b/src/tools/index.ts
@@ -2,8 +2,13 @@ import { stringTools } from '../pages/string';
import { imageTools } from '../pages/image';
import { DefinedTool } from './defineTool';
import { capitalizeFirstLetter } from '../utils/string';
+import { numberTools } from '../pages/number';
-export const tools: DefinedTool[] = [...imageTools, ...stringTools];
+export const tools: DefinedTool[] = [
+ ...imageTools,
+ ...stringTools,
+ ...numberTools
+];
const categoriesDescriptions: { type: string; value: string }[] = [
{
type: 'string',
@@ -14,6 +19,11 @@ const categoriesDescriptions: { type: string; value: string }[] = [
type: 'png',
value:
'Tools for working with PNG images – convert PNGs to JPGs, create transparent PNGs, change PNG colors, crop, rotate, resize PNGs, and much more.'
+ },
+ {
+ type: 'number',
+ value:
+ 'Tools for working with numbers – generate number sequences, convert numbers to words and words to numbers, sort, round, factor numbers, and much more.'
}
];
export const filterTools = (