refactor: use ToolContent

This commit is contained in:
Ibrahima G. Coulibaly
2025-03-09 18:06:27 +00:00
parent 04832bd104
commit 37154c6461
13 changed files with 791 additions and 845 deletions

48
.idea/workspace.xml generated
View File

@@ -4,18 +4,20 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="refactor: use ToolContent"> <list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="feat: missing tools">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/options/CheckboxWithDesc.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/options/CheckboxWithDesc.tsx" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/compress-png/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/png/compress-png/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/list/duplicate/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/duplicate/index.tsx" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/convert-jgp-to-png/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/png/convert-jgp-to-png/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/list/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/index.ts" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/create-transparent/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/png/create-transparent/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/list/truncate/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/truncate/index.tsx" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/pages/tools/json/prettify/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/json/prettify/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/list/unwrap/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/unwrap/index.tsx" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/pages/tools/list/find-most-popular/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/find-most-popular/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/list/wrap/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/wrap/index.tsx" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/pages/tools/list/group/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/group/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/index.ts" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/pages/tools/list/rotate/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/rotate/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/quote/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/quote/index.tsx" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/pages/tools/list/sort/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/sort/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/rot13/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/rot13/index.tsx" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/pages/tools/number/generate/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/number/generate/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/rotate/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/rotate/index.tsx" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/pages/tools/string/remove-duplicate-lines/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/remove-duplicate-lines/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/split/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/split/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/video/gif/change-speed/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/video/gif/change-speed/index.tsx" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -343,15 +345,7 @@
<workItem from="1741475969294" duration="4215000" /> <workItem from="1741475969294" duration="4215000" />
<workItem from="1741494053121" duration="178000" /> <workItem from="1741494053121" duration="178000" />
<workItem from="1741537936314" duration="1294000" /> <workItem from="1741537936314" duration="1294000" />
<workItem from="1741539602311" duration="2441000" /> <workItem from="1741539602311" duration="3899000" />
</task>
<task id="LOCAL-00107" summary="fix: docs">
<option name="closed" value="true" />
<created>1740324026721</created>
<option name="number" value="00107" />
<option name="presentableId" value="LOCAL-00107" />
<option name="project" value="LOCAL" />
<updated>1740324026721</updated>
</task> </task>
<task id="LOCAL-00108" summary="fix: docs"> <task id="LOCAL-00108" summary="fix: docs">
<option name="closed" value="true" /> <option name="closed" value="true" />
@@ -737,7 +731,15 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1741540939154</updated> <updated>1741540939154</updated>
</task> </task>
<option name="localTasksCounter" value="156" /> <task id="LOCAL-00156" summary="feat: missing tools">
<option name="closed" value="true" />
<created>1741542318259</created>
<option name="number" value="00156" />
<option name="presentableId" value="LOCAL-00156" />
<option name="project" value="LOCAL" />
<updated>1741542318259</updated>
</task>
<option name="localTasksCounter" value="157" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@@ -784,7 +786,6 @@
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" /> <option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
<option name="CHECK_NEW_TODO" value="false" /> <option name="CHECK_NEW_TODO" value="false" />
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" /> <option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
<MESSAGE value="refact: examples" />
<MESSAGE value="fix: examples" /> <MESSAGE value="fix: examples" />
<MESSAGE value="feat: json pretty" /> <MESSAGE value="feat: json pretty" />
<MESSAGE value="style: tool categories" /> <MESSAGE value="style: tool categories" />
@@ -809,7 +810,8 @@
<MESSAGE value="chore: remove unnecessary files" /> <MESSAGE value="chore: remove unnecessary files" />
<MESSAGE value="refactor: validateJson" /> <MESSAGE value="refactor: validateJson" />
<MESSAGE value="refactor: use ToolContent" /> <MESSAGE value="refactor: use ToolContent" />
<option name="LAST_COMMIT_MESSAGE" value="refactor: use ToolContent" /> <MESSAGE value="feat: missing tools" />
<option name="LAST_COMMIT_MESSAGE" value="feat: missing tools" />
</component> </component>
<component name="XSLT-Support.FileAssociations.UIState"> <component name="XSLT-Support.FileAssociations.UIState">
<expand /> <expand />

View File

@@ -3,11 +3,11 @@ import React, { useState } from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';
import ToolFileInput from '@components/input/ToolFileInput'; import ToolFileInput from '@components/input/ToolFileInput';
import ToolFileResult from '@components/result/ToolFileResult'; import ToolFileResult from '@components/result/ToolFileResult';
import ToolOptions from '@components/options/ToolOptions';
import TextFieldWithDesc from 'components/options/TextFieldWithDesc'; import TextFieldWithDesc from 'components/options/TextFieldWithDesc';
import ToolInputAndResult from '@components/ToolInputAndResult';
import imageCompression from 'browser-image-compression'; import imageCompression from 'browser-image-compression';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
const initialValues = { const initialValues = {
rate: '50' rate: '50'
@@ -16,7 +16,7 @@ const validationSchema = Yup.object({
// splitSeparator: Yup.string().required('The separator is required') // splitSeparator: Yup.string().required('The separator is required')
}); });
export default function ChangeColorsInPng() { export default function ChangeColorsInPng({ title }: ToolComponentProps) {
const [input, setInput] = useState<File | null>(null); const [input, setInput] = useState<File | null>(null);
const [result, setResult] = useState<File | null>(null); const [result, setResult] = useState<File | null>(null);
const [originalSize, setOriginalSize] = useState<number | null>(null); // Store original file size const [originalSize, setOriginalSize] = useState<number | null>(null); // Store original file size
@@ -52,62 +52,60 @@ export default function ChangeColorsInPng() {
}; };
return ( return (
<Box> <ToolContent
<ToolInputAndResult title={title}
input={ input={input}
<ToolFileInput inputComponent={
value={input} <ToolFileInput
onChange={setInput} value={input}
accept={['image/png']} onChange={setInput}
title={'Input PNG'} accept={['image/png']}
/> title={'Input PNG'}
} />
result={ }
<ToolFileResult resultComponent={
title={'Compressed PNG'} <ToolFileResult
value={result} title={'Compressed PNG'}
extension={'png'} value={result}
/> extension={'png'}
} />
/> }
<ToolOptions initialValues={initialValues}
compute={compute} getGroups={({ values, updateField }) => [
getGroups={({ values, updateField }) => [ {
{ title: 'Compression options',
title: 'Compression options', component: (
component: ( <Box>
<TextFieldWithDesc
value={values.rate}
onOwnChange={(val) => updateField('rate', val)}
description={'Compression rate (1-100)'}
/>
</Box>
)
},
{
title: 'File sizes',
component: (
<Box>
<Box> <Box>
<TextFieldWithDesc {originalSize !== null && (
value={values.rate} <Typography>
onOwnChange={(val) => updateField('rate', val)} Original Size: {(originalSize / 1024).toFixed(2)} KB
description={'Compression rate (1-100)'} </Typography>
/> )}
{compressedSize !== null && (
<Typography>
Compressed Size: {(compressedSize / 1024).toFixed(2)} KB
</Typography>
)}
</Box> </Box>
) </Box>
}, )
{ }
title: 'File sizes', ]}
component: ( compute={compute}
<Box> setInput={setInput}
<Box> />
{originalSize !== null && (
<Typography>
Original Size: {(originalSize / 1024).toFixed(2)} KB
</Typography>
)}
{compressedSize !== null && (
<Typography>
Compressed Size: {(compressedSize / 1024).toFixed(2)} KB
</Typography>
)}
</Box>
</Box>
)
}
]}
initialValues={initialValues}
input={input}
/>
</Box>
); );
} }

View File

@@ -1,15 +1,15 @@
import { Box } from '@mui/material'; import { Box } from '@mui/material';
import ToolInputAndResult from 'components/ToolInputAndResult';
import ToolFileInput from 'components/input/ToolFileInput'; import ToolFileInput from 'components/input/ToolFileInput';
import CheckboxWithDesc from 'components/options/CheckboxWithDesc'; import CheckboxWithDesc from 'components/options/CheckboxWithDesc';
import ColorSelector from 'components/options/ColorSelector'; import ColorSelector from 'components/options/ColorSelector';
import TextFieldWithDesc from 'components/options/TextFieldWithDesc'; import TextFieldWithDesc from 'components/options/TextFieldWithDesc';
import ToolOptions from 'components/options/ToolOptions';
import ToolFileResult from 'components/result/ToolFileResult'; import ToolFileResult from 'components/result/ToolFileResult';
import Color from 'color'; import Color from 'color';
import React, { useState } from 'react'; import React, { useState } from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { areColorsSimilar } from 'utils/color'; import { areColorsSimilar } from 'utils/color';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
const initialValues = { const initialValues = {
enableTransparency: false, enableTransparency: false,
@@ -19,7 +19,7 @@ const initialValues = {
const validationSchema = Yup.object({ const validationSchema = Yup.object({
// splitSeparator: Yup.string().required('The separator is required') // splitSeparator: Yup.string().required('The separator is required')
}); });
export default function ConvertJgpToPng() { export default function ConvertJgpToPng({ title }: ToolComponentProps) {
const [input, setInput] = useState<File | null>(null); const [input, setInput] = useState<File | null>(null);
const [result, setResult] = useState<File | null>(null); const [result, setResult] = useState<File | null>(null);
@@ -97,58 +97,52 @@ export default function ConvertJgpToPng() {
}; };
return ( return (
<Box> <ToolContent
<ToolInputAndResult title={title}
input={ input={input}
<ToolFileInput inputComponent={
value={input} <ToolFileInput
onChange={setInput} value={input}
accept={['image/jpeg']} onChange={setInput}
title={'Input JPG'} accept={['image/jpeg']}
/> title={'Input JPG'}
/>
}
resultComponent={
<ToolFileResult title={'Output PNG'} value={result} extension={'png'} />
}
initialValues={initialValues}
getGroups={({ values, updateField }) => [
{
title: 'PNG Transparency Color',
component: (
<Box>
<CheckboxWithDesc
key="enableTransparency"
title="Enable PNG Transparency"
checked={!!values.enableTransparency}
onChange={(value) => updateField('enableTransparency', value)}
description="Make the color below transparent."
/>
<ColorSelector
value={values.color}
onColorChange={(val) => updateField('color', val)}
description={'With this color (to color)'}
inputProps={{ 'data-testid': 'color-input' }}
/>
<TextFieldWithDesc
value={values.similarity}
onOwnChange={(val) => updateField('similarity', val)}
description={
'Match this % of similar. For example, 10% white will match white and a little bit of gray.'
}
/>
</Box>
)
} }
result={ ]}
<ToolFileResult compute={compute}
title={'Output PNG'} setInput={setInput}
value={result} />
extension={'png'}
/>
}
/>
<ToolOptions
compute={compute}
getGroups={({ values, updateField }) => [
{
title: 'PNG Transparency Color',
component: (
<Box>
<CheckboxWithDesc
key="enableTransparency"
title="Enable PNG Transparency"
checked={!!values.enableTransparency}
onChange={(value) => updateField('enableTransparency', value)}
description="Make the color below transparent."
/>
<ColorSelector
value={values.color}
onColorChange={(val) => updateField('color', val)}
description={'With this color (to color)'}
inputProps={{ 'data-testid': 'color-input' }}
/>
<TextFieldWithDesc
value={values.similarity}
onOwnChange={(val) => updateField('similarity', val)}
description={
'Match this % of similar. For example, 10% white will match white and a little bit of gray.'
}
/>
</Box>
)
}
]}
initialValues={initialValues}
input={input}
/>
</Box>
); );
} }

View File

@@ -3,21 +3,24 @@ import React, { useState } from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';
import ToolFileInput from '@components/input/ToolFileInput'; import ToolFileInput from '@components/input/ToolFileInput';
import ToolFileResult from '@components/result/ToolFileResult'; import ToolFileResult from '@components/result/ToolFileResult';
import ToolOptions from '@components/options/ToolOptions';
import ColorSelector from '@components/options/ColorSelector'; import ColorSelector from '@components/options/ColorSelector';
import Color from 'color'; import Color from 'color';
import TextFieldWithDesc from 'components/options/TextFieldWithDesc'; import TextFieldWithDesc from 'components/options/TextFieldWithDesc';
import ToolInputAndResult from '@components/ToolInputAndResult';
import { areColorsSimilar } from 'utils/color'; import { areColorsSimilar } from 'utils/color';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
import { GetGroupsType } from '@components/options/ToolOptions';
const initialValues = { const initialValues = {
fromColor: 'white', fromColor: 'white',
similarity: '10' similarity: '10'
}; };
const validationSchema = Yup.object({ const validationSchema = Yup.object({
// splitSeparator: Yup.string().required('The separator is required') // splitSeparator: Yup.string().required('The separator is required')
}); });
export default function ChangeColorsInPng() {
export default function CreateTransparent({ title }: ToolComponentProps) {
const [input, setInput] = useState<File | null>(null); const [input, setInput] = useState<File | null>(null);
const [result, setResult] = useState<File | null>(null); const [result, setResult] = useState<File | null>(null);
@@ -76,52 +79,60 @@ export default function ChangeColorsInPng() {
processImage(input, fromRgb, Number(similarity)); processImage(input, fromRgb, Number(similarity));
}; };
const getGroups: GetGroupsType<typeof initialValues> = ({
values,
updateField
}) => [
{
title: 'From color and similarity',
component: (
<Box>
<ColorSelector
value={values.fromColor}
onColorChange={(val) => updateField('fromColor', val)}
description={'Replace this color (from color)'}
inputProps={{ 'data-testid': 'color-input' }}
/>
<TextFieldWithDesc
value={values.similarity}
onOwnChange={(val) => updateField('similarity', val)}
description={
'Match this % of similar colors of the from color. For example, 10% white will match white and a little bit of gray.'
}
/>
</Box>
)
}
];
return ( return (
<Box> <ToolContent
<ToolInputAndResult title={title}
input={ inputComponent={
<ToolFileInput <ToolFileInput
value={input} value={input}
onChange={setInput} onChange={setInput}
accept={['image/png']} accept={['image/png']}
title={'Input PNG'} title={'Input PNG'}
/> />
} }
result={ resultComponent={
<ToolFileResult <ToolFileResult
title={'Transparent PNG'} title={'Transparent PNG'}
value={result} value={result}
extension={'png'} extension={'png'}
/> />
} }
/> initialValues={initialValues}
<ToolOptions getGroups={getGroups}
compute={compute} compute={compute}
getGroups={({ values, updateField }) => [ input={input}
{ validationSchema={validationSchema}
title: 'From color and similarity', toolInfo={{
component: ( title: 'Create Transparent PNG',
<Box> description:
<ColorSelector 'This tool allows you to make specific colors in a PNG image transparent. You can select the color to replace and adjust the similarity threshold to include similar colors.'
value={values.fromColor} }}
onColorChange={(val) => updateField('fromColor', val)} />
description={'Replace this color (from color)'}
inputProps={{ 'data-testid': 'color-input' }}
/>
<TextFieldWithDesc
value={values.similarity}
onOwnChange={(val) => updateField('similarity', val)}
description={
'Match this % of similar colors of the from color. For example, 10% white will match white and a little bit of gray.'
}
/>
</Box>
)
}
]}
initialValues={initialValues}
input={input}
/>
</Box>
); );
} }

View File

@@ -2,10 +2,7 @@ import { Box } from '@mui/material';
import React, { useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
import ToolTextInput from '@components/input/ToolTextInput'; import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult'; import ToolTextResult from '@components/result/ToolTextResult';
import ToolOptions, { GetGroupsType } from '@components/options/ToolOptions';
import { beautifyJson } from './service'; import { beautifyJson } from './service';
import ToolInputAndResult from '@components/ToolInputAndResult';
import ToolInfo from '@components/ToolInfo'; import ToolInfo from '@components/ToolInfo';
import Separator from '@components/Separator'; import Separator from '@components/Separator';
import ToolExamples, { import ToolExamples, {
@@ -16,6 +13,7 @@ import { ToolComponentProps } from '@tools/defineTool';
import RadioWithTextField from '@components/options/RadioWithTextField'; import RadioWithTextField from '@components/options/RadioWithTextField';
import SimpleRadio from '@components/options/SimpleRadio'; import SimpleRadio from '@components/options/SimpleRadio';
import { isNumber, updateNumberField } from '../../../../utils/string'; import { isNumber, updateNumberField } from '../../../../utils/string';
import ToolContent from '@components/ToolContent';
type InitialValuesType = { type InitialValuesType = {
indentationType: 'tab' | 'space'; indentationType: 'tab' | 'space';
@@ -119,72 +117,63 @@ const exampleCards: CardExampleType<InitialValuesType>[] = [
export default function PrettifyJson({ title }: ToolComponentProps) { export default function PrettifyJson({ title }: ToolComponentProps) {
const [input, setInput] = useState<string>(''); const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>(''); const [result, setResult] = useState<string>('');
const formRef = useRef<FormikProps<InitialValuesType>>(null);
const compute = (optionsValues: InitialValuesType, input: any) => { const compute = (optionsValues: InitialValuesType, input: any) => {
const { indentationType, spacesCount } = optionsValues; const { indentationType, spacesCount } = optionsValues;
if (input) setResult(beautifyJson(input, indentationType, spacesCount)); if (input) setResult(beautifyJson(input, indentationType, spacesCount));
}; };
const getGroups: GetGroupsType<InitialValuesType> = ({
values,
updateField
}) => [
{
title: 'Indentation',
component: (
<Box>
<RadioWithTextField
checked={values.indentationType === 'space'}
title={'Use Spaces'}
fieldName={'indentationType'}
description={'Indent output with spaces'}
value={values.spacesCount.toString()}
onRadioClick={() => updateField('indentationType', 'space')}
onTextChange={(val) =>
updateNumberField(val, 'spacesCount', updateField)
}
/>
<SimpleRadio
onClick={() => updateField('indentationType', 'tab')}
checked={values.indentationType === 'tab'}
description={'Indent output with tabs.'}
title={'Use Tabs'}
/>
</Box>
)
}
];
return ( return (
<Box> <Box>
<ToolInputAndResult <ToolContent
input={ title={title}
input={input}
inputComponent={
<ToolTextInput <ToolTextInput
title={'Input JSON'} title={'Input JSON'}
value={input} value={input}
onChange={setInput} onChange={setInput}
/> />
} }
result={<ToolTextResult title={'Pretty JSON'} value={result} />} resultComponent={
/> <ToolTextResult title={'Pretty JSON'} value={result} />
<ToolOptions }
formRef={formRef}
compute={compute}
getGroups={getGroups}
initialValues={initialValues} initialValues={initialValues}
input={input} getGroups={({ values, updateField }) => [
{
title: 'Indentation',
component: (
<Box>
<RadioWithTextField
checked={values.indentationType === 'space'}
title={'Use Spaces'}
fieldName={'indentationType'}
description={'Indent output with spaces'}
value={values.spacesCount.toString()}
onRadioClick={() => updateField('indentationType', 'space')}
onTextChange={(val) =>
updateNumberField(val, 'spacesCount', updateField)
}
/>
<SimpleRadio
onClick={() => updateField('indentationType', 'tab')}
checked={values.indentationType === 'tab'}
description={'Indent output with tabs.'}
title={'Use Tabs'}
/>
</Box>
)
}
]}
compute={compute}
setInput={setInput}
exampleCards={exampleCards}
/> />
<ToolInfo <ToolInfo
title="What Is a JSON Prettifier?" title="What Is a JSON Prettifier?"
description="This tool adds consistent formatting to the data in JavaScript Object Notation (JSON) format. This transformation makes the JSON code more readable, making it easier to understand and edit. The program parses the JSON data structure into tokens and then reformats them by adding indentation and line breaks. If the data is hierarchial, then it adds indentation at the beginning of lines to visually show the depth of the JSON and adds newlines to break long single-line JSON arrays into multiple shorter, more readable ones. Additionally, this utility can remove unnecessary spaces and tabs from your JSON code (especially leading and trailing whitespaces), making it more compact. You can choose the line indentation method in the options: indent with spaces or indent with tabs. When using spaces, you can also specify how many spaces to use for each indentation level (usually 2 or 4 spaces). " description="This tool adds consistent formatting to the data in JavaScript Object Notation (JSON) format. This transformation makes the JSON code more readable, making it easier to understand and edit. The program parses the JSON data structure into tokens and then reformats them by adding indentation and line breaks. If the data is hierarchial, then it adds indentation at the beginning of lines to visually show the depth of the JSON and adds newlines to break long single-line JSON arrays into multiple shorter, more readable ones. Additionally, this utility can remove unnecessary spaces and tabs from your JSON code (especially leading and trailing whitespaces), making it more compact. You can choose the line indentation method in the options: indent with spaces or indent with tabs. When using spaces, you can also specify how many spaces to use for each indentation level (usually 2 or 4 spaces). "
/> />
<Separator backgroundColor="#5581b5" margin="50px" /> <Separator backgroundColor="#5581b5" margin="50px" />
<ToolExamples
title={title}
exampleCards={exampleCards}
getGroups={getGroups}
formRef={formRef}
setInput={setInput}
/>
</Box> </Box>
); );
} }

View File

@@ -2,18 +2,18 @@ import { Box } from '@mui/material';
import React, { useState } from 'react'; import React, { useState } from 'react';
import ToolTextInput from '@components/input/ToolTextInput'; import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult'; import ToolTextResult from '@components/result/ToolTextResult';
import ToolOptions from '@components/options/ToolOptions';
import { import {
DisplayFormat, DisplayFormat,
SortingMethod, SortingMethod,
SplitOperatorType, SplitOperatorType,
TopItemsList TopItemsList
} from './service'; } from './service';
import ToolInputAndResult from '@components/ToolInputAndResult';
import SimpleRadio from '@components/options/SimpleRadio'; import SimpleRadio from '@components/options/SimpleRadio';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import CheckboxWithDesc from '@components/options/CheckboxWithDesc'; import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
import SelectWithDesc from '@components/options/SelectWithDesc'; import SelectWithDesc from '@components/options/SelectWithDesc';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
const initialValues = { const initialValues = {
splitSeparatorType: 'symbol' as SplitOperatorType, splitSeparatorType: 'symbol' as SplitOperatorType,
@@ -41,7 +41,7 @@ const splitOperators: {
} }
]; ];
export default function FindMostPopular() { export default function FindMostPopular({ title }: ToolComponentProps) {
const [input, setInput] = useState<string>(''); const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>(''); const [result, setResult] = useState<string>('');
const compute = (optionsValues: typeof initialValues, input: any) => { const compute = (optionsValues: typeof initialValues, input: any) => {
@@ -70,98 +70,94 @@ export default function FindMostPopular() {
}; };
return ( return (
<Box> <ToolContent
<ToolInputAndResult title={title}
input={ input={input}
<ToolTextInput inputComponent={
title={'Input list'} <ToolTextInput title={'Input list'} value={input} onChange={setInput} />
value={input} }
onChange={setInput} resultComponent={
/> <ToolTextResult title={'Most popular items'} value={result} />
}
initialValues={initialValues}
getGroups={({ values, updateField }) => [
{
title: 'How to Extract List Items?',
component: (
<Box>
{splitOperators.map(({ title, description, type }) => (
<SimpleRadio
key={type}
onClick={() => updateField('splitSeparatorType', type)}
title={title}
description={description}
checked={values.splitSeparatorType === type}
/>
))}
<TextFieldWithDesc
description={'Set a delimiting symbol or regular expression.'}
value={values.splitSeparator}
onOwnChange={(val) => updateField('splitSeparator', val)}
/>
</Box>
)
},
{
title: 'Item comparison',
component: (
<Box>
<CheckboxWithDesc
title={'Remove empty items'}
description={'Ignore empty items from comparison.'}
checked={values.deleteEmptyItems}
onChange={(value) => updateField('deleteEmptyItems', value)}
/>
<CheckboxWithDesc
title={'Trim top list items'}
description={
'Remove leading and trailing spaces before comparing items'
}
checked={values.trimItems}
onChange={(value) => updateField('trimItems', value)}
/>
<CheckboxWithDesc
title={'Ignore Item Case'}
description={'Compare all list items in lowercase.'}
checked={values.ignoreItemCase}
onChange={(value) => updateField('ignoreItemCase', value)}
/>
</Box>
)
},
{
title: 'Top item output format',
component: (
<Box>
<SelectWithDesc
selected={values.displayFormat}
options={[
{ label: 'Show item percentage', value: 'percentage' },
{ label: 'Show item count', value: 'count' },
{ label: 'Show item total', value: 'total' }
]}
onChange={(value) => updateField('displayFormat', value)}
description={'How to display the most popular list items?'}
/>
<SelectWithDesc
selected={values.sortingMethod}
options={[
{ label: 'Sort Alphabetically', value: 'alphabetic' },
{ label: 'Sort by count', value: 'count' }
]}
onChange={(value) => updateField('sortingMethod', value)}
description={'Select a sorting method.'}
/>
</Box>
)
} }
result={<ToolTextResult title={'Most popular items'} value={result} />} ]}
/> compute={compute}
<ToolOptions setInput={setInput}
compute={compute} />
getGroups={({ values, updateField }) => [
{
title: 'How to Extract List Items?',
component: (
<Box>
{splitOperators.map(({ title, description, type }) => (
<SimpleRadio
key={type}
onClick={() => updateField('splitSeparatorType', type)}
title={title}
description={description}
checked={values.splitSeparatorType === type}
/>
))}
<TextFieldWithDesc
description={'Set a delimiting symbol or regular expression.'}
value={values.splitSeparator}
onOwnChange={(val) => updateField('splitSeparator', val)}
/>
</Box>
)
},
{
title: 'Item comparison',
component: (
<Box>
<CheckboxWithDesc
title={'Remove empty items'}
description={'Ignore empty items from comparison.'}
checked={values.deleteEmptyItems}
onChange={(value) => updateField('deleteEmptyItems', value)}
/>
<CheckboxWithDesc
title={'Trim top list items'}
description={
'Remove leading and trailing spaces before comparing items'
}
checked={values.trimItems}
onChange={(value) => updateField('trimItems', value)}
/>
<CheckboxWithDesc
title={'Ignore Item Case'}
description={'Compare all list items in lowercase.'}
checked={values.ignoreItemCase}
onChange={(value) => updateField('ignoreItemCase', value)}
/>
</Box>
)
},
{
title: 'Top item output format',
component: (
<Box>
<SelectWithDesc
selected={values.displayFormat}
options={[
{ label: 'Show item percentage', value: 'percentage' },
{ label: 'Show item count', value: 'count' },
{ label: 'Show item total', value: 'total' }
]}
onChange={(value) => updateField('displayFormat', value)}
description={'How to display the most popular list items?'}
/>
<SelectWithDesc
selected={values.sortingMethod}
options={[
{ label: 'Sort Alphabetically', value: 'alphabetic' },
{ label: 'Sort by count', value: 'count' }
]}
onChange={(value) => updateField('sortingMethod', value)}
description={'Select a sorting method.'}
/>
</Box>
)
}
]}
initialValues={initialValues}
input={input}
/>
</Box>
); );
} }

View File

@@ -2,13 +2,13 @@ import { Box } from '@mui/material';
import React, { useState } from 'react'; import React, { useState } from 'react';
import ToolTextInput from '@components/input/ToolTextInput'; import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult'; import ToolTextResult from '@components/result/ToolTextResult';
import ToolOptions from '@components/options/ToolOptions';
import { groupList, SplitOperatorType } from './service'; import { groupList, SplitOperatorType } from './service';
import ToolInputAndResult from '@components/ToolInputAndResult';
import SimpleRadio from '@components/options/SimpleRadio'; import SimpleRadio from '@components/options/SimpleRadio';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import CheckboxWithDesc from '@components/options/CheckboxWithDesc'; import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
import { formatNumber } from '../../../../utils/number'; import { formatNumber } from '../../../../utils/number';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
const initialValues = { const initialValues = {
splitOperatorType: 'symbol' as SplitOperatorType, splitOperatorType: 'symbol' as SplitOperatorType,
@@ -39,7 +39,7 @@ const splitOperators: {
} }
]; ];
export default function FindUnique() { export default function FindUnique({ title }: ToolComponentProps) {
const [input, setInput] = useState<string>(''); const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>(''); const [result, setResult] = useState<string>('');
const compute = (optionsValues: typeof initialValues, input: any) => { const compute = (optionsValues: typeof initialValues, input: any) => {
@@ -74,110 +74,106 @@ export default function FindUnique() {
}; };
return ( return (
<Box> <ToolContent
<ToolInputAndResult title={title}
input={ input={input}
<ToolTextInput inputComponent={
title={'Input list'} <ToolTextInput title={'Input list'} value={input} onChange={setInput} />
value={input} }
onChange={setInput} resultComponent={
/> <ToolTextResult title={'Grouped items'} value={result} />
}
initialValues={initialValues}
getGroups={({ values, updateField }) => [
{
title: 'Input Item Separator',
component: (
<Box>
{splitOperators.map(({ title, description, type }) => (
<SimpleRadio
key={type}
onClick={() => updateField('splitOperatorType', type)}
title={title}
description={description}
checked={values.splitOperatorType === type}
/>
))}
<TextFieldWithDesc
description={'Set a delimiting symbol or regular expression.'}
value={values.splitSeparator}
onOwnChange={(val) => updateField('splitSeparator', val)}
/>
</Box>
)
},
{
title: 'Group Size and Separators',
component: (
<Box>
<TextFieldWithDesc
value={values.groupNumber}
description={'Number of items in a group'}
type={'number'}
onOwnChange={(value) =>
updateField('groupNumber', formatNumber(value, 1))
}
/>
<TextFieldWithDesc
value={values.itemSeparator}
description={'Item separator character'}
onOwnChange={(value) => updateField('itemSeparator', value)}
/>
<TextFieldWithDesc
value={values.groupSeparator}
description={'Group separator character'}
onOwnChange={(value) => updateField('groupSeparator', value)}
/>
<TextFieldWithDesc
value={values.leftWrap}
description={"Group's left wrap symbol."}
onOwnChange={(value) => updateField('leftWrap', value)}
/>
<TextFieldWithDesc
value={values.rightWrap}
description={"Group's right wrap symbol."}
onOwnChange={(value) => updateField('rightWrap', value)}
/>
</Box>
)
},
{
title: 'Empty Items and Padding',
component: (
<Box>
<CheckboxWithDesc
title={'Delete Empty Items'}
description={
"Ignore empty items and don't include them in the groups."
}
checked={values.deleteEmptyItems}
onChange={(value) => updateField('deleteEmptyItems', value)}
/>
<CheckboxWithDesc
title={'Pad Non-full Groups'}
description={
'Fill non-full groups with a custom item (enter below).'
}
checked={values.padNonFullGroup}
onChange={(value) => updateField('padNonFullGroup', value)}
/>
<TextFieldWithDesc
value={values.paddingChar}
description={
'Use this character or item to pad non-full groups.'
}
onOwnChange={(value) => updateField('paddingChar', value)}
/>
</Box>
)
} }
result={<ToolTextResult title={'Grouped items'} value={result} />} ]}
/> compute={compute}
<ToolOptions setInput={setInput}
compute={compute} />
getGroups={({ values, updateField }) => [
{
title: 'Input Item Separator',
component: (
<Box>
{splitOperators.map(({ title, description, type }) => (
<SimpleRadio
key={type}
onClick={() => updateField('splitOperatorType', type)}
title={title}
description={description}
checked={values.splitOperatorType === type}
/>
))}
<TextFieldWithDesc
description={'Set a delimiting symbol or regular expression.'}
value={values.splitSeparator}
onOwnChange={(val) => updateField('splitSeparator', val)}
/>
</Box>
)
},
{
title: 'Group Size and Separators',
component: (
<Box>
<TextFieldWithDesc
value={values.groupNumber}
description={'Number of items in a group'}
type={'number'}
onOwnChange={(value) =>
updateField('groupNumber', formatNumber(value, 1))
}
/>
<TextFieldWithDesc
value={values.itemSeparator}
description={'Item separator character'}
onOwnChange={(value) => updateField('itemSeparator', value)}
/>
<TextFieldWithDesc
value={values.groupSeparator}
description={'Group separator character'}
onOwnChange={(value) => updateField('groupSeparator', value)}
/>
<TextFieldWithDesc
value={values.leftWrap}
description={"Group's left wrap symbol."}
onOwnChange={(value) => updateField('leftWrap', value)}
/>
<TextFieldWithDesc
value={values.rightWrap}
description={"Group's right wrap symbol."}
onOwnChange={(value) => updateField('rightWrap', value)}
/>
</Box>
)
},
{
title: 'Empty Items and Padding',
component: (
<Box>
<CheckboxWithDesc
title={'Delete Empty Items'}
description={
"Ignore empty items and don't include them in the groups."
}
checked={values.deleteEmptyItems}
onChange={(value) => updateField('deleteEmptyItems', value)}
/>
<CheckboxWithDesc
title={'Pad Non-full Groups'}
description={
'Fill non-full groups with a custom item (enter below).'
}
checked={values.padNonFullGroup}
onChange={(value) => updateField('padNonFullGroup', value)}
/>
<TextFieldWithDesc
value={values.paddingChar}
description={
'Use this character or item to pad non-full groups.'
}
onOwnChange={(value) => updateField('paddingChar', value)}
/>
</Box>
)
}
]}
initialValues={initialValues}
input={input}
/>
</Box>
); );
} }

View File

@@ -2,12 +2,12 @@ import { Box } from '@mui/material';
import React, { useState } from 'react'; import React, { useState } from 'react';
import ToolTextInput from '@components/input/ToolTextInput'; import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult'; import ToolTextResult from '@components/result/ToolTextResult';
import ToolOptions from '@components/options/ToolOptions';
import { rotateList, SplitOperatorType } from './service'; import { rotateList, SplitOperatorType } from './service';
import ToolInputAndResult from '@components/ToolInputAndResult';
import SimpleRadio from '@components/options/SimpleRadio'; import SimpleRadio from '@components/options/SimpleRadio';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import { formatNumber } from '../../../../utils/number'; import { formatNumber } from '../../../../utils/number';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
const initialValues = { const initialValues = {
splitOperatorType: 'symbol' as SplitOperatorType, splitOperatorType: 'symbol' as SplitOperatorType,
@@ -52,7 +52,7 @@ const rotationDirections: {
} }
]; ];
export default function Rotate() { export default function Rotate({ title }: ToolComponentProps) {
const [input, setInput] = useState<string>(''); const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>(''); const [result, setResult] = useState<string>('');
const compute = (optionsValues: typeof initialValues, input: any) => { const compute = (optionsValues: typeof initialValues, input: any) => {
@@ -72,82 +72,74 @@ export default function Rotate() {
}; };
return ( return (
<Box> <ToolContent
<ToolInputAndResult title={title}
input={ input={input}
<ToolTextInput inputComponent={
title={'Input list'} <ToolTextInput title={'Input list'} value={input} onChange={setInput} />
value={input} }
onChange={setInput} resultComponent={<ToolTextResult title={'Rotated list'} value={result} />}
/> initialValues={initialValues}
getGroups={({ values, updateField }) => [
{
title: 'Item split mode',
component: (
<Box>
{splitOperators.map(({ title, description, type }) => (
<SimpleRadio
key={type}
onClick={() => updateField('splitOperatorType', type)}
title={title}
description={description}
checked={values.splitOperatorType === type}
/>
))}
<TextFieldWithDesc
description={'Set a delimiting symbol or regular expression.'}
value={values.splitSeparator}
onOwnChange={(val) => updateField('splitSeparator', val)}
/>
</Box>
)
},
{
title: 'Rotation Direction and Count',
component: (
<Box>
{rotationDirections.map(({ title, description, value }) => (
<SimpleRadio
key={`${value}`}
onClick={() => updateField('right', value)}
title={title}
description={description}
checked={values.right === value}
/>
))}
<TextFieldWithDesc
description={'Number of items to rotate'}
value={values.step}
onOwnChange={(val) => updateField('step', formatNumber(val, 1))}
/>
</Box>
)
},
{
title: 'Rotated List Joining Symbol',
component: (
<Box>
<TextFieldWithDesc
value={values.joinSeparator}
onOwnChange={(value) => updateField('joinSeparator', value)}
description={
'Enter the character that goes between items in the rotated list.'
}
/>
</Box>
)
} }
result={<ToolTextResult title={'Rotated list'} value={result} />} ]}
/> compute={compute}
<ToolOptions setInput={setInput}
compute={compute} />
getGroups={({ values, updateField }) => [
{
title: 'Item split mode',
component: (
<Box>
{splitOperators.map(({ title, description, type }) => (
<SimpleRadio
key={type}
onClick={() => updateField('splitOperatorType', type)}
title={title}
description={description}
checked={values.splitOperatorType === type}
/>
))}
<TextFieldWithDesc
description={'Set a delimiting symbol or regular expression.'}
value={values.splitSeparator}
onOwnChange={(val) => updateField('splitSeparator', val)}
/>
</Box>
)
},
{
title: 'Rotation Direction and Count',
component: (
<Box>
{rotationDirections.map(({ title, description, value }) => (
<SimpleRadio
key={`${value}`}
onClick={() => updateField('right', value)}
title={title}
description={description}
checked={values.right === value}
/>
))}
<TextFieldWithDesc
description={'Number of items to rotate'}
value={values.step}
onOwnChange={(val) =>
updateField('step', formatNumber(val, 1))
}
/>
</Box>
)
},
{
title: 'Rotated List Joining Symbol',
component: (
<Box>
<TextFieldWithDesc
value={values.joinSeparator}
onOwnChange={(value) => updateField('joinSeparator', value)}
description={
'Enter the character that goes between items in the rotated list.'
}
/>
</Box>
)
}
]}
initialValues={initialValues}
input={input}
/>
</Box>
); );
} }

View File

@@ -2,13 +2,13 @@ import { Box } from '@mui/material';
import React, { useState } from 'react'; import React, { useState } from 'react';
import ToolTextInput from '@components/input/ToolTextInput'; import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult'; import ToolTextResult from '@components/result/ToolTextResult';
import ToolOptions from '@components/options/ToolOptions';
import { Sort, SortingMethod, SplitOperatorType } from './service'; import { Sort, SortingMethod, SplitOperatorType } from './service';
import ToolInputAndResult from '@components/ToolInputAndResult';
import SimpleRadio from '@components/options/SimpleRadio'; import SimpleRadio from '@components/options/SimpleRadio';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import CheckboxWithDesc from '@components/options/CheckboxWithDesc'; import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
import SelectWithDesc from '@components/options/SelectWithDesc'; import SelectWithDesc from '@components/options/SelectWithDesc';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
const initialValues = { const initialValues = {
splitSeparatorType: 'symbol' as SplitOperatorType, splitSeparatorType: 'symbol' as SplitOperatorType,
@@ -36,7 +36,7 @@ const splitOperators: {
} }
]; ];
export default function SplitText() { export default function SplitText({ title }: ToolComponentProps) {
const [input, setInput] = useState<string>(''); const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>(''); const [result, setResult] = useState<string>('');
const compute = (optionsValues: typeof initialValues, input: any) => { const compute = (optionsValues: typeof initialValues, input: any) => {
@@ -65,101 +65,95 @@ export default function SplitText() {
}; };
return ( return (
<Box> <ToolContent
<ToolInputAndResult title={title}
input={ input={input}
<ToolTextInput inputComponent={
title={'Input list'} <ToolTextInput title={'Input list'} value={input} onChange={setInput} />
value={input} }
onChange={setInput} resultComponent={<ToolTextResult title={'Sorted list'} value={result} />}
/> initialValues={initialValues}
getGroups={({ values, updateField }) => [
{
title: 'Input item separator',
component: (
<Box>
{splitOperators.map(({ title, description, type }) => (
<SimpleRadio
key={type}
onClick={() => updateField('splitSeparatorType', type)}
title={title}
description={description}
checked={values.splitSeparatorType === type}
/>
))}
<TextFieldWithDesc
description={'Set a delimiting symbol or regular expression.'}
value={values.splitSeparator}
onOwnChange={(val) => updateField('splitSeparator', val)}
/>
</Box>
)
},
{
title: 'Sort method',
component: (
<Box>
<SelectWithDesc
selected={values.sortingMethod}
options={[
{ label: 'Sort Alphabetically', value: 'alphabetic' },
{ label: 'Sort Numerically', value: 'numeric' },
{ label: 'Sort by Length', value: 'length' }
]}
onChange={(value) => updateField('sortingMethod', value)}
description={'Select a sorting method.'}
/>
<SelectWithDesc
selected={values.increasing}
options={[
{ label: 'Increasing order', value: true },
{ label: 'Decreasing order', value: false }
]}
onChange={(value) => {
updateField('increasing', value);
}}
description={'Select a sorting order.'}
/>
<CheckboxWithDesc
title={'Case Sensitive Sort'}
description={
'Sort uppercase and lowercase items separately. Capital letters precede lowercase letters in an ascending list. (Works only in alphabetical sorting mode.)'
}
checked={values.caseSensitive}
onChange={(val) => updateField('caseSensitive', val)}
/>
</Box>
)
},
{
title: 'Sorted item properties',
component: (
<Box>
<TextFieldWithDesc
description={
'Use this symbol as a joiner between items in a sorted list.'
}
value={values.joinSeparator}
onOwnChange={(val) => updateField('joinSeparator', val)}
/>
<CheckboxWithDesc
title={'Remove duplicates'}
description={'Delete duplicate list items.'}
checked={values.removeDuplicated}
onChange={(val) => updateField('removeDuplicated', val)}
/>
</Box>
)
} }
result={<ToolTextResult title={'Sorted list'} value={result} />} ]}
/> compute={compute}
<ToolOptions setInput={setInput}
compute={compute} />
getGroups={({ values, updateField }) => [
{
title: 'Input item separator',
component: (
<Box>
{splitOperators.map(({ title, description, type }) => (
<SimpleRadio
key={type}
onClick={() => updateField('splitSeparatorType', type)}
title={title}
description={description}
checked={values.splitSeparatorType === type}
/>
))}
<TextFieldWithDesc
description={'Set a delimiting symbol or regular expression.'}
value={values.splitSeparator}
onOwnChange={(val) => updateField('splitSeparator', val)}
/>
</Box>
)
},
{
title: 'Sort method',
component: (
<Box>
<SelectWithDesc
selected={values.sortingMethod}
options={[
{ label: 'Sort Alphabetically', value: 'alphabetic' },
{ label: 'Sort Numerically', value: 'numeric' },
{ label: 'Sort by Length', value: 'length' }
]}
onChange={(value) => updateField('sortingMethod', value)}
description={'Select a sorting method.'}
/>
<SelectWithDesc
selected={values.increasing}
options={[
{ label: 'Increasing order', value: true },
{ label: 'Decreasing order', value: false }
]}
onChange={(value) => {
updateField('increasing', value);
}}
description={'Select a sorting order.'}
/>
<CheckboxWithDesc
title={'Case Sensitive Sort'}
description={
'Sort uppercase and lowercase items separately. Capital letters precede lowercase letters in an ascending list. (Works only in alphabetical sorting mode.)'
}
checked={values.caseSensitive}
onChange={(val) => updateField('caseSensitive', val)}
/>
</Box>
)
},
{
title: 'Sorted item properties',
component: (
<Box>
<TextFieldWithDesc
description={
'Use this symbol as a joiner between items in a sorted list.'
}
value={values.joinSeparator}
onOwnChange={(val) => updateField('joinSeparator', val)}
/>
<CheckboxWithDesc
title={'Remove duplicates'}
description={'Delete duplicate list items.'}
checked={values.removeDuplicated}
onChange={(val) => updateField('removeDuplicated', val)}
/>
</Box>
)
}
]}
initialValues={initialValues}
input={input}
/>
</Box>
); );
} }

View File

@@ -1,10 +1,10 @@
import { Box } from '@mui/material'; import { Box } from '@mui/material';
import React, { useState } from 'react'; import React, { useState } from 'react';
import ToolTextResult from '@components/result/ToolTextResult'; import ToolTextResult from '@components/result/ToolTextResult';
import ToolOptions from '@components/options/ToolOptions';
import { listOfIntegers } from './service'; import { listOfIntegers } from './service';
import ToolInputAndResult from '@components/ToolInputAndResult';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
const initialValues = { const initialValues = {
firstValue: '1', firstValue: '1',
@@ -12,68 +12,69 @@ const initialValues = {
step: '1', step: '1',
separator: '\\n' separator: '\\n'
}; };
export default function SplitText() {
export default function GenerateNumbers({ title }: ToolComponentProps) {
const [result, setResult] = useState<string>(''); const [result, setResult] = useState<string>('');
const compute = (optionsValues: typeof initialValues) => {
const { firstValue, numberOfNumbers, separator, step } = optionsValues;
setResult(
listOfIntegers(
Number(firstValue),
Number(numberOfNumbers),
Number(step),
separator
)
);
};
return ( return (
<Box> <ToolContent
<ToolInputAndResult title={title}
result={<ToolTextResult title={'Total'} value={result} />} initialValues={initialValues}
/> getGroups={({ values, updateField }) => [
<ToolOptions {
getGroups={({ values, updateField }) => [ title: 'Arithmetic sequence option',
{ component: (
title: 'Arithmetic sequence option', <Box>
component: (
<Box>
<TextFieldWithDesc
description={'Start sequence from this number.'}
value={values.firstValue}
onOwnChange={(val) => updateField('firstValue', val)}
type={'number'}
/>
<TextFieldWithDesc
description={'Increase each element by this amount'}
value={values.step}
onOwnChange={(val) => updateField('step', val)}
type={'number'}
/>
<TextFieldWithDesc
description={'Number of elements in sequence.'}
value={values.numberOfNumbers}
onOwnChange={(val) => updateField('numberOfNumbers', val)}
type={'number'}
/>
</Box>
)
},
{
title: 'Separator',
component: (
<TextFieldWithDesc <TextFieldWithDesc
description={ description={'Start sequence from this number.'}
'Separate elements in the arithmetic sequence by this character.' value={values.firstValue}
} onOwnChange={(val) => updateField('firstValue', val)}
value={values.separator} type={'number'}
onOwnChange={(val) => updateField('separator', val)}
/> />
) <TextFieldWithDesc
} description={'Increase each element by this amount'}
]} value={values.step}
compute={(optionsValues) => { onOwnChange={(val) => updateField('step', val)}
const { firstValue, numberOfNumbers, separator, step } = type={'number'}
optionsValues; />
setResult( <TextFieldWithDesc
listOfIntegers( description={'Number of elements in sequence.'}
Number(firstValue), value={values.numberOfNumbers}
Number(numberOfNumbers), onOwnChange={(val) => updateField('numberOfNumbers', val)}
Number(step), type={'number'}
separator />
) </Box>
); )
}} },
initialValues={initialValues} {
/> title: 'Separator',
</Box> component: (
<TextFieldWithDesc
description={
'Separate elements in the arithmetic sequence by this character.'
}
value={values.separator}
onOwnChange={(val) => updateField('separator', val)}
/>
)
}
]}
compute={compute}
resultComponent={
<ToolTextResult title={'Generated numbers'} value={result} />
}
/>
); );
} }

View File

@@ -2,10 +2,8 @@ import { Box } from '@mui/material';
import React, { useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
import ToolTextInput from '@components/input/ToolTextInput'; import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult'; import ToolTextResult from '@components/result/ToolTextResult';
import ToolOptions, { GetGroupsType } from '@components/options/ToolOptions';
import SimpleRadio from '@components/options/SimpleRadio'; import SimpleRadio from '@components/options/SimpleRadio';
import CheckboxWithDesc from '@components/options/CheckboxWithDesc'; import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
import ToolInputAndResult from '@components/ToolInputAndResult';
import ToolExamples, { import ToolExamples, {
CardExampleType CardExampleType
} from '@components/examples/ToolExamples'; } from '@components/examples/ToolExamples';
@@ -16,6 +14,7 @@ import removeDuplicateLines, {
DuplicateRemoverOptions, DuplicateRemoverOptions,
NewlineOption NewlineOption
} from './service'; } from './service';
import ToolContent from '@components/ToolContent';
// Initial values for our form // Initial values for our form
const initialValues: DuplicateRemoverOptions = { const initialValues: DuplicateRemoverOptions = {
@@ -174,7 +173,6 @@ Elderberry`,
export default function RemoveDuplicateLines({ title }: ToolComponentProps) { export default function RemoveDuplicateLines({ title }: ToolComponentProps) {
const [input, setInput] = useState<string>(''); const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>(''); const [result, setResult] = useState<string>('');
const formRef = useRef<FormikProps<typeof initialValues>>(null);
const computeExternal = ( const computeExternal = (
optionsValues: typeof initialValues, optionsValues: typeof initialValues,
@@ -183,78 +181,65 @@ export default function RemoveDuplicateLines({ title }: ToolComponentProps) {
setResult(removeDuplicateLines(inputText, optionsValues)); setResult(removeDuplicateLines(inputText, optionsValues));
}; };
const getGroups: GetGroupsType<typeof initialValues> = ({
values,
updateField
}) => [
{
title: 'Operation Mode',
component: operationModes.map(({ title, description, value }) => (
<SimpleRadio
key={value}
checked={value === values.mode}
title={title}
description={description}
onClick={() => updateField('mode', value)}
/>
))
},
{
title: 'Newlines, Tabs and Spaces',
component: [
...newlineOptions.map(({ title, description, value }) => (
<SimpleRadio
key={value}
checked={value === values.newlines}
title={title}
description={description}
onClick={() => updateField('newlines', value)}
/>
)),
<CheckboxWithDesc
key="trimTextLines"
checked={values.trimTextLines}
title="Trim Text Lines"
description="Before filtering uniques, remove tabs and spaces from the beginning and end of all lines."
onChange={(checked) => updateField('trimTextLines', checked)}
/>
]
},
{
title: 'Sort Lines',
component: [
<CheckboxWithDesc
key="sortLines"
checked={values.sortLines}
title="Sort the Output Lines"
description="After removing the duplicates, sort the unique lines."
onChange={(checked) => updateField('sortLines', checked)}
/>
]
}
];
return ( return (
<Box> <ToolContent
<ToolInputAndResult title={title}
input={<ToolTextInput value={input} onChange={setInput} />} input={input}
result={ inputComponent={<ToolTextInput value={input} onChange={setInput} />}
<ToolTextResult title={'Text without duplicates'} value={result} /> resultComponent={
<ToolTextResult title={'Text without duplicates'} value={result} />
}
initialValues={initialValues}
getGroups={({ values, updateField }) => [
{
title: 'Operation Mode',
component: operationModes.map(({ title, description, value }) => (
<SimpleRadio
key={value}
checked={value === values.mode}
title={title}
description={description}
onClick={() => updateField('mode', value)}
/>
))
},
{
title: 'Newlines, Tabs and Spaces',
component: [
...newlineOptions.map(({ title, description, value }) => (
<SimpleRadio
key={value}
checked={value === values.newlines}
title={title}
description={description}
onClick={() => updateField('newlines', value)}
/>
)),
<CheckboxWithDesc
key="trimTextLines"
checked={values.trimTextLines}
title="Trim Text Lines"
description="Before filtering uniques, remove tabs and spaces from the beginning and end of all lines."
onChange={(checked) => updateField('trimTextLines', checked)}
/>
]
},
{
title: 'Sort Lines',
component: [
<CheckboxWithDesc
key="sortLines"
checked={values.sortLines}
title="Sort the Output Lines"
description="After removing the duplicates, sort the unique lines."
onChange={(checked) => updateField('sortLines', checked)}
/>
]
} }
/> ]}
<ToolOptions compute={computeExternal}
compute={computeExternal} setInput={setInput}
getGroups={getGroups} exampleCards={exampleCards}
initialValues={initialValues} />
input={input}
/>
<ToolExamples
title={title}
exampleCards={exampleCards}
getGroups={getGroups}
formRef={formRef}
setInput={setInput}
/>
</Box>
); );
} }

View File

@@ -2,16 +2,15 @@ import { Box } from '@mui/material';
import React, { useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
import ToolTextInput from '@components/input/ToolTextInput'; import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult'; import ToolTextResult from '@components/result/ToolTextResult';
import ToolOptions, { GetGroupsType } from '@components/options/ToolOptions';
import { compute, SplitOperatorType } from './service'; import { compute, SplitOperatorType } from './service';
import RadioWithTextField from '@components/options/RadioWithTextField'; import RadioWithTextField from '@components/options/RadioWithTextField';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import ToolInputAndResult from '@components/ToolInputAndResult';
import ToolExamples, { import ToolExamples, {
CardExampleType CardExampleType
} from '@components/examples/ToolExamples'; } from '@components/examples/ToolExamples';
import { ToolComponentProps } from '@tools/defineTool'; import { ToolComponentProps } from '@tools/defineTool';
import { FormikProps } from 'formik'; import { FormikProps } from 'formik';
import ToolContent from '@components/ToolContent';
const initialValues = { const initialValues = {
splitSeparatorType: 'symbol' as SplitOperatorType, splitSeparatorType: 'symbol' as SplitOperatorType,
@@ -135,8 +134,11 @@ easy`,
export default function SplitText({ title }: ToolComponentProps) { export default function SplitText({ title }: ToolComponentProps) {
const [input, setInput] = useState<string>(''); const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>(''); const [result, setResult] = useState<string>('');
const formRef = useRef<FormikProps<typeof initialValues>>(null);
const computeExternal = (optionsValues: typeof initialValues, input: any) => { const computeExternal = (
optionsValues: typeof initialValues,
input: string
) => {
const { const {
splitSeparatorType, splitSeparatorType,
outputSeparator, outputSeparator,
@@ -163,56 +165,44 @@ export default function SplitText({ title }: ToolComponentProps) {
); );
}; };
const getGroups: GetGroupsType<typeof initialValues> = ({
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}
/>
))
}
];
return ( return (
<Box> <ToolContent
<ToolInputAndResult title={title}
input={<ToolTextInput value={input} onChange={setInput} />} input={input}
result={<ToolTextResult title={'Text pieces'} value={result} />} inputComponent={<ToolTextInput value={input} onChange={setInput} />}
/> resultComponent={<ToolTextResult title={'Text pieces'} value={result} />}
<ToolOptions initialValues={initialValues}
compute={computeExternal} getGroups={({ values, updateField }) => [
getGroups={getGroups} {
initialValues={initialValues} title: 'Split separator options',
input={input} component: splitOperators.map(({ title, description, type }) => (
/> <RadioWithTextField
<ToolExamples key={type}
title={title} checked={type === values.splitSeparatorType}
exampleCards={exampleCards} title={title}
getGroups={getGroups} fieldName={'splitSeparatorType'}
formRef={formRef} description={description}
setInput={setInput} value={values[`${type}Value`]}
/> onRadioClick={() => updateField('splitSeparatorType', type)}
</Box> 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}
/>
))
}
]}
compute={computeExternal}
setInput={setInput}
exampleCards={exampleCards}
/>
); );
} }

View File

@@ -3,12 +3,12 @@ import React, { useState } from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';
import ToolFileInput from '@components/input/ToolFileInput'; import ToolFileInput from '@components/input/ToolFileInput';
import ToolFileResult from '@components/result/ToolFileResult'; import ToolFileResult from '@components/result/ToolFileResult';
import ToolOptions from '@components/options/ToolOptions';
import TextFieldWithDesc from 'components/options/TextFieldWithDesc'; import TextFieldWithDesc from 'components/options/TextFieldWithDesc';
import ToolInputAndResult from '@components/ToolInputAndResult';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { FrameOptions, GifReader, GifWriter } from 'omggif'; import { FrameOptions, GifReader, GifWriter } from 'omggif';
import { gifBinaryToFile } from '../../../../../utils/gif'; import { gifBinaryToFile } from '@utils/gif';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
const initialValues = { const initialValues = {
newSpeed: 200 newSpeed: 200
@@ -16,11 +16,11 @@ const initialValues = {
const validationSchema = Yup.object({ const validationSchema = Yup.object({
// splitSeparator: Yup.string().required('The separator is required') // splitSeparator: Yup.string().required('The separator is required')
}); });
export default function ChangeSpeed() { export default function ChangeSpeed({ title }: ToolComponentProps) {
const [input, setInput] = useState<File | null>(null); const [input, setInput] = useState<File | null>(null);
const [result, setResult] = useState<File | null>(null); const [result, setResult] = useState<File | null>(null);
const compute = (optionsValues: typeof initialValues, input: File) => { const compute = (optionsValues: typeof initialValues, input: File | null) => {
if (!input) return; if (!input) return;
const { newSpeed } = optionsValues; const { newSpeed } = optionsValues;
@@ -104,45 +104,43 @@ export default function ChangeSpeed() {
processImage(input, newSpeed); processImage(input, newSpeed);
}; };
return ( return (
<Box> <ToolContent
<ToolInputAndResult title={title}
input={ input={input}
<ToolFileInput inputComponent={
value={input} <ToolFileInput
onChange={setInput} value={input}
accept={['image/gif']} onChange={setInput}
title={'Input GIF'} accept={['image/gif']}
/> title={'Input GIF'}
/>
}
resultComponent={
<ToolFileResult
title={'Output GIF with new speed'}
value={result}
extension={'gif'}
/>
}
initialValues={initialValues}
getGroups={({ values, updateField }) => [
{
title: 'New GIF speed',
component: (
<Box>
<TextFieldWithDesc
value={values.newSpeed}
onOwnChange={(val) => updateField('newSpeed', Number(val))}
description={'Default new GIF speed.'}
InputProps={{ endAdornment: <Typography>ms</Typography> }}
type={'number'}
/>
</Box>
)
} }
result={ ]}
<ToolFileResult compute={compute}
title={'Output GIF with new speed'} setInput={setInput}
value={result} />
extension={'gif'}
/>
}
/>
<ToolOptions
compute={compute}
getGroups={({ values, updateField }) => [
{
title: 'New GIF speed',
component: (
<Box>
<TextFieldWithDesc
value={values.newSpeed}
onOwnChange={(val) => updateField('newSpeed', Number(val))}
description={'Default new GIF speed.'}
InputProps={{ endAdornment: <Typography>ms</Typography> }}
type={'number'}
/>
</Box>
)
}
]}
initialValues={initialValues}
input={input}
/>
</Box>
); );
} }