feat: sum numbers init

This commit is contained in:
Ibrahima G. Coulibaly
2024-06-25 03:11:48 +01:00
parent bea0332020
commit cfe0d324fe
17 changed files with 324 additions and 49 deletions

View File

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

48
.idea/workspace.xml generated
View File

@@ -4,20 +4,24 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="feat: change color in png finished">
<change afterPath="$PROJECT_DIR$/src/components/options/ToolOptionGroups.tsx" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/pages/string/to-morse/index.tsx" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/pages/string/to-morse/meta.ts" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/pages/string/to-morse/service.ts" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/pages/string/to-morse/to-morse.service.test.ts" afterDir="false" />
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="feat: string to morse">
<change afterPath="$PROJECT_DIR$/src/components/options/SimpleRadio.tsx" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/pages/number/index.ts" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/pages/number/sum/index.tsx" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/pages/number/sum/meta.ts" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/pages/number/sum/service.ts" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/pages/number/sum/sum.service.test.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.eslintrc" beforeDir="false" afterPath="$PROJECT_DIR$/.eslintrc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/scripts/create-tool.mjs" beforeDir="false" afterPath="$PROJECT_DIR$/scripts/create-tool.mjs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/options/ColorSelector.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/options/ColorSelector.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/options/RadioWithTextField.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/options/RadioWithTextField.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/options/ToolOptionGroups.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/options/ToolOptionGroups.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/config/uiConfig.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/config/uiConfig.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/home/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/home/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/string/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/string/index.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/string/join/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/string/join/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/string/split/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/string/split/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/string/to-morse/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/string/to-morse/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/tools/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/tools/index.ts" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -187,15 +191,7 @@
<workItem from="1719166718305" duration="1783000" />
<workItem from="1719168519203" duration="17675000" />
<workItem from="1719197816332" duration="1453000" />
<workItem from="1719273044735" duration="4589000" />
</task>
<task id="LOCAL-00001" summary="feat: use vite and ts">
<option name="closed" value="true" />
<created>1718816900959</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1718816900959</updated>
<workItem from="1719273044735" duration="8300000" />
</task>
<task id="LOCAL-00002" summary="feat: initial commit">
<option name="closed" value="true" />
@@ -581,7 +577,15 @@
<option name="project" value="LOCAL" />
<updated>1719275214988</updated>
</task>
<option name="localTasksCounter" value="50" />
<task id="LOCAL-00050" summary="feat: string to morse">
<option name="closed" value="true" />
<created>1719277679968</created>
<option name="number" value="00050" />
<option name="presentableId" value="LOCAL-00050" />
<option name="project" value="LOCAL" />
<updated>1719277679969</updated>
</task>
<option name="localTasksCounter" value="51" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@@ -602,7 +606,6 @@
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
<option name="CHECK_NEW_TODO" value="false" />
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
<MESSAGE value="chore: remove prebuild" />
<MESSAGE value="chore: idea config" />
<MESSAGE value="feat: react helmet" />
<MESSAGE value="feat: tools normalized" />
@@ -627,7 +630,8 @@
<MESSAGE value="feat: change colors in png" />
<MESSAGE value="chore: ResultFooter" />
<MESSAGE value="feat: change color in png finished" />
<option name="LAST_COMMIT_MESSAGE" value="feat: change color in png finished" />
<MESSAGE value="feat: string to morse" />
<option name="LAST_COMMIT_MESSAGE" value="feat: string to morse" />
</component>
<component name="XSLT-Support.FileAssociations.UIState">
<expand />

View File

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

View File

@@ -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<ColorSelectorProps> = ({
onChange={handleColorChange}
/>
</Stack>
<Typography fontSize={descriptionFontSize}>{description}</Typography>
<Typography fontSize={globalDescriptionFontSize}>
{description}
</Typography>
</Box>
);
};

View File

@@ -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 = <T,>({
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 (
<Box>
<Stack
direction={'row'}
sx={{ mt: 2, mb: 1, cursor: 'pointer' }}
onClick={onChange}
alignItems={'center'}
spacing={1}
>
<Field type="radio" name={fieldName} value={type} onChange={onChange} />
<Typography>{title}</Typography>
</Stack>
<SimpleRadio
value={radioValue}
onChange={onChange}
fieldName={fieldName}
title={title}
description={typeDescription}
/>
<TextFieldWithDesc
value={value}
onChange={onTextChange}

View File

@@ -0,0 +1,46 @@
import { Box, Stack } from '@mui/material';
import { Field } from 'formik';
import Typography from '@mui/material/Typography';
import { globalDescriptionFontSize } from '../../config/uiConfig';
import React from 'react';
interface SimpleRadioProps {
onChange: () => void;
fieldName: string;
value: any;
title: string;
description?: string;
}
export default function SimpleRadio({
onChange,
fieldName,
value,
title,
description
}: SimpleRadioProps) {
return (
<Box>
<Stack
direction={'row'}
sx={{ mt: 2, mb: 1, cursor: 'pointer' }}
onClick={onChange}
alignItems={'center'}
spacing={1}
>
<Field
type="radio"
name={fieldName}
value={value}
onChange={onChange}
/>
<Typography>{title}</Typography>
</Stack>
{description && (
<Typography ml={2} fontSize={globalDescriptionFontSize}>
{description}
</Typography>
)}
</Box>
);
}

View File

@@ -11,7 +11,9 @@ export default function ToolOptionGroups({
<Stack direction={'row'} spacing={2}>
{groups.map((group) => (
<Box key={group.title}>
<Typography fontSize={22}>{group.title}</Typography>
<Typography mb={1} fontSize={22}>
{group.title}
</Typography>
{group.component}
</Box>
))}

View File

@@ -1,2 +1,2 @@
export const globalInputHeight = 300;
export const descriptionFontSize = 12;
export const globalDescriptionFontSize = 12;

View File

@@ -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() {

View File

@@ -0,0 +1,3 @@
import { tool as numberSum } from './sum/meta';
export const numberTools = [numberSum];

View File

@@ -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<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 { 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 (
<Box>
<Grid container spacing={2}>
<Grid item xs={6}>
<ToolTextInput value={input} onChange={setInput} />
</Grid>
<Grid item xs={6}>
<ToolTextResult title={'Text pieces'} value={result} />
</Grid>
</Grid>
<ToolOptions>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={() => {}}
>
{({ setFieldValue, values }) => (
<Stack direction={'row'} spacing={2}>
<FormikListenerComponent />
<ToolOptionGroups
groups={[
{
title: 'Number extraction',
component: extractionTypes.map(
({
title,
description,
type,
withTextField,
textValueAccessor
}) =>
withTextField ? (
<RadioWithTextField
key={type}
radioValue={type}
title={title}
fieldName={'extractionType'}
description={description}
value={
textValueAccessor
? values[textValueAccessor].toString()
: ''
}
onRadioChange={(type) =>
setFieldValue('extractionType', type)
}
onTextChange={(val) =>
setFieldValue(textValueAccessor ?? '', val)
}
/>
) : (
<SimpleRadio
key={title}
onChange={() =>
setFieldValue('extractionType', type)
}
fieldName={'extractionType'}
value={values.extractionType}
description={description}
title={title}
/>
)
)
},
{
title: 'Running Sum',
component: (
<CheckboxWithDesc
title={'Print Running Sum'}
description={
"Display the sum as it's calculated step by step."
}
checked={values.printRunningSum}
onChange={(value) =>
setFieldValue('printRunningSum', value)
}
/>
)
}
]}
/>
</Stack>
)}
</Formik>
</ToolOptions>
</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('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'))
});

View File

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

View File

@@ -0,0 +1,6 @@
import { expect, describe, it } from 'vitest';
// import { } from './service';
//
// describe('sum', () => {
//
// })

View File

@@ -151,12 +151,12 @@ export default function SplitText() {
({ title, description, type }) => (
<RadioWithTextField
key={type}
type={type}
radioValue={type}
title={title}
fieldName={'splitSeparatorType'}
description={description}
value={values[`${type}Value`]}
onTypeChange={(type) =>
onRadioChange={(type) =>
setFieldValue('splitSeparatorType', type)
}
onTextChange={(val) =>

View File

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

View File

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