mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-16 20:49:30 +02:00
refactor: toolOptions
This commit is contained in:
36
.idea/workspace.xml
generated
36
.idea/workspace.xml
generated
@@ -4,9 +4,16 @@
|
|||||||
<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="chore: loading screen">
|
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="chore: shuffle tools search">
|
||||||
<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/Hero.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/Hero.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/components/options/ToolOptions.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/options/ToolOptions.tsx" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/pages/image/png/change-colors-in-png/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/image/png/change-colors-in-png/index.tsx" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/pages/image/png/create-transparent/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/image/png/create-transparent/index.tsx" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/pages/number/sum/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/number/sum/index.tsx" 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/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/string/to-morse/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" />
|
||||||
@@ -193,15 +200,8 @@
|
|||||||
<workItem from="1719294110005" duration="3842000" />
|
<workItem from="1719294110005" duration="3842000" />
|
||||||
<workItem from="1719339559458" duration="303000" />
|
<workItem from="1719339559458" duration="303000" />
|
||||||
<workItem from="1719340295244" duration="772000" />
|
<workItem from="1719340295244" duration="772000" />
|
||||||
<workItem from="1719363272227" duration="330000" />
|
<workItem from="1719363272227" duration="390000" />
|
||||||
</task>
|
<workItem from="1719379971872" duration="4208000" />
|
||||||
<task id="LOCAL-00016" summary="ubf jn">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1719006829016</created>
|
|
||||||
<option name="number" value="00016" />
|
|
||||||
<option name="presentableId" value="LOCAL-00016" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1719006829016</updated>
|
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00017" summary="ubf jn">
|
<task id="LOCAL-00017" summary="ubf jn">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@@ -587,7 +587,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1719360545177</updated>
|
<updated>1719360545177</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="65" />
|
<task id="LOCAL-00065" summary="chore: shuffle tools search">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1719363656541</created>
|
||||||
|
<option name="number" value="00065" />
|
||||||
|
<option name="presentableId" value="LOCAL-00065" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1719363656541</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="66" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -608,7 +616,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="fix: missing files" />
|
|
||||||
<MESSAGE value="fix: create tool" />
|
<MESSAGE value="fix: create tool" />
|
||||||
<MESSAGE value="fix: build" />
|
<MESSAGE value="fix: build" />
|
||||||
<MESSAGE value="chore: CODEOWNERS" />
|
<MESSAGE value="chore: CODEOWNERS" />
|
||||||
@@ -633,7 +640,8 @@
|
|||||||
<MESSAGE value="feat: make tool responsive" />
|
<MESSAGE value="feat: make tool responsive" />
|
||||||
<MESSAGE value="chore: make tool examples responsive" />
|
<MESSAGE value="chore: make tool examples responsive" />
|
||||||
<MESSAGE value="chore: loading screen" />
|
<MESSAGE value="chore: loading screen" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="chore: loading screen" />
|
<MESSAGE value="chore: shuffle tools search" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="chore: shuffle tools search" />
|
||||||
</component>
|
</component>
|
||||||
<component name="XSLT-Support.FileAssociations.UIState">
|
<component name="XSLT-Support.FileAssociations.UIState">
|
||||||
<expand />
|
<expand />
|
||||||
|
@@ -1,9 +1,8 @@
|
|||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import React, { ReactNode } from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import { Box, Stack } from '@mui/material';
|
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
|
|
||||||
interface ToolOptionGroup {
|
export interface ToolOptionGroup {
|
||||||
title: string;
|
title: string;
|
||||||
component: ReactNode;
|
component: ReactNode;
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,44 @@
|
|||||||
import { Box, Stack, useTheme } from '@mui/material';
|
import { Box, Stack, useTheme } from '@mui/material';
|
||||||
import SettingsIcon from '@mui/icons-material/Settings';
|
import SettingsIcon from '@mui/icons-material/Settings';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import React, { ReactNode } from 'react';
|
import React, { ReactNode, RefObject, useContext, useEffect } from 'react';
|
||||||
|
import { Formik, FormikProps, FormikValues, useFormikContext } from 'formik';
|
||||||
|
import ToolOptionGroups, { ToolOptionGroup } from './ToolOptionGroups';
|
||||||
|
import { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';
|
||||||
|
|
||||||
export default function ToolOptions({ children }: { children: ReactNode }) {
|
export default function ToolOptions<T extends FormikValues>({
|
||||||
|
children,
|
||||||
|
initialValues,
|
||||||
|
validationSchema,
|
||||||
|
compute,
|
||||||
|
input,
|
||||||
|
getGroups,
|
||||||
|
formRef
|
||||||
|
}: {
|
||||||
|
children?: ReactNode;
|
||||||
|
initialValues: T;
|
||||||
|
validationSchema: any | (() => any);
|
||||||
|
compute: (optionsValues: T, input: any) => void;
|
||||||
|
input: any;
|
||||||
|
getGroups: (formikProps: FormikProps<T>) => ToolOptionGroup[];
|
||||||
|
formRef?: RefObject<FormikProps<T>>;
|
||||||
|
}) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const FormikListenerComponent = () => {
|
||||||
|
const { values } = useFormikContext<typeof initialValues>();
|
||||||
|
const { showSnackBar } = useContext(CustomSnackBarContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
compute(values, input);
|
||||||
|
} catch (exception: unknown) {
|
||||||
|
if (exception instanceof Error)
|
||||||
|
showSnackBar(exception.message, 'error');
|
||||||
|
}
|
||||||
|
}, [values, showSnackBar]);
|
||||||
|
|
||||||
|
return null; // This component doesn't render anything
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -19,7 +53,22 @@ export default function ToolOptions({ children }: { children: ReactNode }) {
|
|||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
<Typography fontSize={22}>Tool options</Typography>
|
<Typography fontSize={22}>Tool options</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Box mt={2}>{children}</Box>
|
<Box mt={2}>
|
||||||
|
<Formik
|
||||||
|
innerRef={formRef}
|
||||||
|
initialValues={initialValues}
|
||||||
|
validationSchema={validationSchema}
|
||||||
|
onSubmit={() => {}}
|
||||||
|
>
|
||||||
|
{(formikProps) => (
|
||||||
|
<Stack direction={'row'} spacing={2}>
|
||||||
|
<FormikListenerComponent />
|
||||||
|
<ToolOptionGroups groups={getGroups(formikProps)} />
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,13 @@
|
|||||||
import { Box } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import React, { useEffect, useState } from 'react';
|
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 ToolOptions from '../../../../components/options/ToolOptions';
|
||||||
import { Formik, useFormikContext } from 'formik';
|
|
||||||
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 ToolInputAndResult from '../../../../components/ToolInputAndResult';
|
||||||
import ToolOptionGroups from '../../../../components/options/ToolOptionGroups';
|
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
fromColor: 'white',
|
fromColor: 'white',
|
||||||
@@ -23,86 +21,81 @@ export default function ChangeColorsInPng() {
|
|||||||
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 FormikListenerComponent = ({ input }: { input: File }) => {
|
const compute = (optionsValues: typeof initialValues, input: any) => {
|
||||||
const { values } = useFormikContext<typeof initialValues>();
|
const { fromColor, toColor, similarity } = optionsValues;
|
||||||
const { fromColor, toColor, similarity } = values;
|
let fromRgb: [number, number, number];
|
||||||
|
let toRgb: [number, number, number];
|
||||||
|
try {
|
||||||
|
//@ts-ignore
|
||||||
|
fromRgb = Color(fromColor).rgb().array();
|
||||||
|
//@ts-ignore
|
||||||
|
toRgb = Color(toColor).rgb().array();
|
||||||
|
} catch (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const processImage = async (
|
||||||
|
file: File,
|
||||||
|
fromColor: [number, number, number],
|
||||||
|
toColor: [number, number, number],
|
||||||
|
similarity: number
|
||||||
|
) => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (ctx == null) return;
|
||||||
|
const img = new Image();
|
||||||
|
|
||||||
useEffect(() => {
|
img.src = URL.createObjectURL(file);
|
||||||
let fromRgb: [number, number, number];
|
await img.decode();
|
||||||
let toRgb: [number, number, number];
|
|
||||||
try {
|
canvas.width = img.width;
|
||||||
//@ts-ignore
|
canvas.height = img.height;
|
||||||
fromRgb = Color(fromColor).rgb().array();
|
ctx.drawImage(img, 0, 0);
|
||||||
//@ts-ignore
|
|
||||||
toRgb = Color(toColor).rgb().array();
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
} catch (err) {
|
const data: Uint8ClampedArray = imageData.data;
|
||||||
return;
|
|
||||||
}
|
const colorDistance = (
|
||||||
const processImage = async (
|
c1: [number, number, number],
|
||||||
file: File,
|
c2: [number, number, number]
|
||||||
fromColor: [number, number, number],
|
|
||||||
toColor: [number, number, number],
|
|
||||||
similarity: number
|
|
||||||
) => {
|
) => {
|
||||||
const canvas = document.createElement('canvas');
|
return Math.sqrt(
|
||||||
const ctx = canvas.getContext('2d');
|
Math.pow(c1[0] - c2[0], 2) +
|
||||||
if (ctx == null) return;
|
Math.pow(c1[1] - c2[1], 2) +
|
||||||
const img = new Image();
|
Math.pow(c1[2] - c2[2], 2)
|
||||||
|
|
||||||
img.src = URL.createObjectURL(file);
|
|
||||||
await img.decode();
|
|
||||||
|
|
||||||
canvas.width = img.width;
|
|
||||||
canvas.height = img.height;
|
|
||||||
ctx.drawImage(img, 0, 0);
|
|
||||||
|
|
||||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
||||||
const data: Uint8ClampedArray = imageData.data;
|
|
||||||
|
|
||||||
const colorDistance = (
|
|
||||||
c1: [number, number, number],
|
|
||||||
c2: [number, number, number]
|
|
||||||
) => {
|
|
||||||
return Math.sqrt(
|
|
||||||
Math.pow(c1[0] - c2[0], 2) +
|
|
||||||
Math.pow(c1[1] - c2[1], 2) +
|
|
||||||
Math.pow(c1[2] - c2[2], 2)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const maxColorDistance = Math.sqrt(
|
|
||||||
Math.pow(255, 2) + Math.pow(255, 2) + Math.pow(255, 2)
|
|
||||||
);
|
);
|
||||||
const similarityThreshold = (similarity / 100) * maxColorDistance;
|
|
||||||
|
|
||||||
for (let i = 0; i < data.length; i += 4) {
|
|
||||||
const currentColor: [number, number, number] = [
|
|
||||||
data[i],
|
|
||||||
data[i + 1],
|
|
||||||
data[i + 2]
|
|
||||||
];
|
|
||||||
if (colorDistance(currentColor, fromColor) <= similarityThreshold) {
|
|
||||||
data[i] = toColor[0]; // Red
|
|
||||||
data[i + 1] = toColor[1]; // Green
|
|
||||||
data[i + 2] = toColor[2]; // Blue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.putImageData(imageData, 0, 0);
|
|
||||||
|
|
||||||
canvas.toBlob((blob) => {
|
|
||||||
if (blob) {
|
|
||||||
const newFile = new File([blob], file.name, { type: 'image/png' });
|
|
||||||
setResult(newFile);
|
|
||||||
}
|
|
||||||
}, 'image/png');
|
|
||||||
};
|
};
|
||||||
|
const maxColorDistance = Math.sqrt(
|
||||||
|
Math.pow(255, 2) + Math.pow(255, 2) + Math.pow(255, 2)
|
||||||
|
);
|
||||||
|
const similarityThreshold = (similarity / 100) * maxColorDistance;
|
||||||
|
|
||||||
processImage(input, fromRgb, toRgb, Number(similarity));
|
for (let i = 0; i < data.length; i += 4) {
|
||||||
}, [input, fromColor, toColor]);
|
const currentColor: [number, number, number] = [
|
||||||
|
data[i],
|
||||||
|
data[i + 1],
|
||||||
|
data[i + 2]
|
||||||
|
];
|
||||||
|
if (colorDistance(currentColor, fromColor) <= similarityThreshold) {
|
||||||
|
data[i] = toColor[0]; // Red
|
||||||
|
data[i + 1] = toColor[1]; // Green
|
||||||
|
data[i + 2] = toColor[2]; // Blue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
|
||||||
|
canvas.toBlob((blob) => {
|
||||||
|
if (blob) {
|
||||||
|
const newFile = new File([blob], file.name, {
|
||||||
|
type: 'image/png'
|
||||||
|
});
|
||||||
|
setResult(newFile);
|
||||||
|
}
|
||||||
|
}, 'image/png');
|
||||||
|
};
|
||||||
|
|
||||||
|
processImage(input, fromRgb, toRgb, Number(similarity));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<ToolInputAndResult
|
<ToolInputAndResult
|
||||||
@@ -122,47 +115,38 @@ export default function ChangeColorsInPng() {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ToolOptions>
|
<ToolOptions
|
||||||
<Formik
|
compute={compute}
|
||||||
initialValues={initialValues}
|
getGroups={({ values, setFieldValue }) => [
|
||||||
validationSchema={validationSchema}
|
{
|
||||||
onSubmit={() => {}}
|
title: 'From color and to color',
|
||||||
>
|
component: (
|
||||||
{({ setFieldValue, values }) => (
|
<Box>
|
||||||
<Box>
|
<ColorSelector
|
||||||
{input && <FormikListenerComponent input={input} />}
|
value={values.fromColor}
|
||||||
<ToolOptionGroups
|
onChange={(val) => setFieldValue('fromColor', val)}
|
||||||
groups={[
|
description={'Replace this color (from color)'}
|
||||||
{
|
/>
|
||||||
title: 'From color and to color',
|
<ColorSelector
|
||||||
component: (
|
value={values.toColor}
|
||||||
<Box>
|
onChange={(val) => setFieldValue('toColor', val)}
|
||||||
<ColorSelector
|
description={'With this color (to color)'}
|
||||||
value={values.fromColor}
|
/>
|
||||||
onChange={(val) => setFieldValue('fromColor', val)}
|
<TextFieldWithDesc
|
||||||
description={'Replace this color (from color)'}
|
value={values.similarity}
|
||||||
/>
|
onChange={(val) => setFieldValue('similarity', val)}
|
||||||
<ColorSelector
|
description={
|
||||||
value={values.toColor}
|
'Match this % of similar colors of the from color. For example, 10% white will match white and a little bit of gray.'
|
||||||
onChange={(val) => setFieldValue('toColor', val)}
|
|
||||||
description={'With this color (to color)'}
|
|
||||||
/>
|
|
||||||
<TextFieldWithDesc
|
|
||||||
value={values.similarity}
|
|
||||||
onChange={(val) => setFieldValue('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>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
]}
|
/>
|
||||||
/>
|
</Box>
|
||||||
</Box>
|
)
|
||||||
)}
|
}
|
||||||
</Formik>
|
]}
|
||||||
</ToolOptions>
|
initialValues={initialValues}
|
||||||
|
input={input}
|
||||||
|
validationSchema={validationSchema}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,13 @@
|
|||||||
import { Box } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import React, { useEffect, useState } from 'react';
|
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 ToolOptions from '../../../../components/options/ToolOptions';
|
||||||
import { Formik, useFormikContext } from 'formik';
|
|
||||||
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 ToolInputAndResult from '../../../../components/ToolInputAndResult';
|
||||||
import ToolOptionGroups from '../../../../components/options/ToolOptionGroups';
|
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
fromColor: 'white',
|
fromColor: 'white',
|
||||||
@@ -22,78 +20,73 @@ export default function ChangeColorsInPng() {
|
|||||||
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 FormikListenerComponent = ({ input }: { input: File }) => {
|
const compute = (optionsValues: typeof initialValues, input: any) => {
|
||||||
const { values } = useFormikContext<typeof initialValues>();
|
const { fromColor, similarity } = optionsValues;
|
||||||
const { fromColor, similarity } = values;
|
|
||||||
|
|
||||||
useEffect(() => {
|
let fromRgb: [number, number, number];
|
||||||
let fromRgb: [number, number, number];
|
try {
|
||||||
try {
|
//@ts-ignore
|
||||||
//@ts-ignore
|
fromRgb = Color(fromColor).rgb().array();
|
||||||
fromRgb = Color(fromColor).rgb().array();
|
} catch (err) {
|
||||||
} catch (err) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
const processImage = async (
|
||||||
const processImage = async (
|
file: File,
|
||||||
file: File,
|
fromColor: [number, number, number],
|
||||||
fromColor: [number, number, number],
|
similarity: number
|
||||||
similarity: number
|
) => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (ctx == null) return;
|
||||||
|
const img = new Image();
|
||||||
|
|
||||||
|
img.src = URL.createObjectURL(file);
|
||||||
|
await img.decode();
|
||||||
|
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
|
||||||
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
|
const data: Uint8ClampedArray = imageData.data;
|
||||||
|
|
||||||
|
const colorDistance = (
|
||||||
|
c1: [number, number, number],
|
||||||
|
c2: [number, number, number]
|
||||||
) => {
|
) => {
|
||||||
const canvas = document.createElement('canvas');
|
return Math.sqrt(
|
||||||
const ctx = canvas.getContext('2d');
|
Math.pow(c1[0] - c2[0], 2) +
|
||||||
if (ctx == null) return;
|
Math.pow(c1[1] - c2[1], 2) +
|
||||||
const img = new Image();
|
Math.pow(c1[2] - c2[2], 2)
|
||||||
|
|
||||||
img.src = URL.createObjectURL(file);
|
|
||||||
await img.decode();
|
|
||||||
|
|
||||||
canvas.width = img.width;
|
|
||||||
canvas.height = img.height;
|
|
||||||
ctx.drawImage(img, 0, 0);
|
|
||||||
|
|
||||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
||||||
const data: Uint8ClampedArray = imageData.data;
|
|
||||||
|
|
||||||
const colorDistance = (
|
|
||||||
c1: [number, number, number],
|
|
||||||
c2: [number, number, number]
|
|
||||||
) => {
|
|
||||||
return Math.sqrt(
|
|
||||||
Math.pow(c1[0] - c2[0], 2) +
|
|
||||||
Math.pow(c1[1] - c2[1], 2) +
|
|
||||||
Math.pow(c1[2] - c2[2], 2)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const maxColorDistance = Math.sqrt(
|
|
||||||
Math.pow(255, 2) + Math.pow(255, 2) + Math.pow(255, 2)
|
|
||||||
);
|
);
|
||||||
const similarityThreshold = (similarity / 100) * maxColorDistance;
|
|
||||||
|
|
||||||
for (let i = 0; i < data.length; i += 4) {
|
|
||||||
const currentColor: [number, number, number] = [
|
|
||||||
data[i],
|
|
||||||
data[i + 1],
|
|
||||||
data[i + 2]
|
|
||||||
];
|
|
||||||
if (colorDistance(currentColor, fromColor) <= similarityThreshold) {
|
|
||||||
data[i + 3] = 0; // Set alpha to 0 (transparent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.putImageData(imageData, 0, 0);
|
|
||||||
|
|
||||||
canvas.toBlob((blob) => {
|
|
||||||
if (blob) {
|
|
||||||
const newFile = new File([blob], file.name, { type: 'image/png' });
|
|
||||||
setResult(newFile);
|
|
||||||
}
|
|
||||||
}, 'image/png');
|
|
||||||
};
|
};
|
||||||
|
const maxColorDistance = Math.sqrt(
|
||||||
|
Math.pow(255, 2) + Math.pow(255, 2) + Math.pow(255, 2)
|
||||||
|
);
|
||||||
|
const similarityThreshold = (similarity / 100) * maxColorDistance;
|
||||||
|
|
||||||
processImage(input, fromRgb, Number(similarity));
|
for (let i = 0; i < data.length; i += 4) {
|
||||||
}, [input, fromColor]);
|
const currentColor: [number, number, number] = [
|
||||||
|
data[i],
|
||||||
|
data[i + 1],
|
||||||
|
data[i + 2]
|
||||||
|
];
|
||||||
|
if (colorDistance(currentColor, fromColor) <= similarityThreshold) {
|
||||||
|
data[i + 3] = 0; // Set alpha to 0 (transparent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
|
||||||
|
canvas.toBlob((blob) => {
|
||||||
|
if (blob) {
|
||||||
|
const newFile = new File([blob], file.name, { type: 'image/png' });
|
||||||
|
setResult(newFile);
|
||||||
|
}
|
||||||
|
}, 'image/png');
|
||||||
|
};
|
||||||
|
|
||||||
|
processImage(input, fromRgb, Number(similarity));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -115,42 +108,33 @@ export default function ChangeColorsInPng() {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ToolOptions>
|
<ToolOptions
|
||||||
<Formik
|
compute={compute}
|
||||||
initialValues={initialValues}
|
getGroups={({ values, setFieldValue }) => [
|
||||||
validationSchema={validationSchema}
|
{
|
||||||
onSubmit={() => {}}
|
title: 'From color and similarity',
|
||||||
>
|
component: (
|
||||||
{({ setFieldValue, values }) => (
|
<Box>
|
||||||
<Box>
|
<ColorSelector
|
||||||
{input && <FormikListenerComponent input={input} />}
|
value={values.fromColor}
|
||||||
<ToolOptionGroups
|
onChange={(val) => setFieldValue('fromColor', val)}
|
||||||
groups={[
|
description={'Replace this color (from color)'}
|
||||||
{
|
/>
|
||||||
title: 'From color and similarity',
|
<TextFieldWithDesc
|
||||||
component: (
|
value={values.similarity}
|
||||||
<Box>
|
onChange={(val) => setFieldValue('similarity', val)}
|
||||||
<ColorSelector
|
description={
|
||||||
value={values.fromColor}
|
'Match this % of similar colors of the from color. For example, 10% white will match white and a little bit of gray.'
|
||||||
onChange={(val) => setFieldValue('fromColor', val)}
|
|
||||||
description={'Replace this color (from color)'}
|
|
||||||
/>
|
|
||||||
<TextFieldWithDesc
|
|
||||||
value={values.similarity}
|
|
||||||
onChange={(val) => setFieldValue('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>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
]}
|
/>
|
||||||
/>
|
</Box>
|
||||||
</Box>
|
)
|
||||||
)}
|
}
|
||||||
</Formik>
|
]}
|
||||||
</ToolOptions>
|
initialValues={initialValues}
|
||||||
|
input={input}
|
||||||
|
validationSchema={validationSchema}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,14 +1,11 @@
|
|||||||
import { Box, Stack } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import React, { useContext, useEffect, 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 { Formik, useFormikContext } from 'formik';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import ToolOptions from '../../../components/options/ToolOptions';
|
import ToolOptions from '../../../components/options/ToolOptions';
|
||||||
import { compute, NumberExtractionType } from './service';
|
import { compute, NumberExtractionType } from './service';
|
||||||
import { CustomSnackBarContext } from '../../../contexts/CustomSnackBarContext';
|
|
||||||
import RadioWithTextField from '../../../components/options/RadioWithTextField';
|
import RadioWithTextField from '../../../components/options/RadioWithTextField';
|
||||||
import ToolOptionGroups from '../../../components/options/ToolOptionGroups';
|
|
||||||
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 ToolInputAndResult from '../../../components/ToolInputAndResult';
|
||||||
@@ -44,25 +41,7 @@ const extractionTypes: {
|
|||||||
export default function SplitText() {
|
export default function SplitText() {
|
||||||
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 { 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({
|
const validationSchema = Yup.object({
|
||||||
// splitSeparator: Yup.string().required('The separator is required')
|
// splitSeparator: Yup.string().required('The separator is required')
|
||||||
});
|
});
|
||||||
@@ -73,81 +52,71 @@ export default function SplitText() {
|
|||||||
input={<ToolTextInput value={input} onChange={setInput} />}
|
input={<ToolTextInput value={input} onChange={setInput} />}
|
||||||
result={<ToolTextResult title={'Total'} value={result} />}
|
result={<ToolTextResult title={'Total'} value={result} />}
|
||||||
/>
|
/>
|
||||||
<ToolOptions>
|
<ToolOptions
|
||||||
<Formik
|
getGroups={({ values, setFieldValue }) => [
|
||||||
initialValues={initialValues}
|
{
|
||||||
validationSchema={validationSchema}
|
title: 'Number extraction',
|
||||||
onSubmit={() => {}}
|
component: extractionTypes.map(
|
||||||
>
|
({
|
||||||
{({ setFieldValue, values }) => (
|
title,
|
||||||
<Stack direction={'row'} spacing={2}>
|
description,
|
||||||
<FormikListenerComponent />
|
type,
|
||||||
<ToolOptionGroups
|
withTextField,
|
||||||
groups={[
|
textValueAccessor
|
||||||
{
|
}) =>
|
||||||
title: 'Number extraction',
|
withTextField ? (
|
||||||
component: extractionTypes.map(
|
<RadioWithTextField
|
||||||
({
|
key={type}
|
||||||
title,
|
radioValue={type}
|
||||||
description,
|
title={title}
|
||||||
type,
|
fieldName={'extractionType'}
|
||||||
withTextField,
|
description={description}
|
||||||
textValueAccessor
|
value={
|
||||||
}) =>
|
textValueAccessor
|
||||||
withTextField ? (
|
? values[textValueAccessor].toString()
|
||||||
<RadioWithTextField
|
: ''
|
||||||
key={type}
|
}
|
||||||
radioValue={type}
|
onRadioChange={(type) =>
|
||||||
title={title}
|
setFieldValue('extractionType', type)
|
||||||
fieldName={'extractionType'}
|
}
|
||||||
description={description}
|
onTextChange={(val) =>
|
||||||
value={
|
textValueAccessor
|
||||||
textValueAccessor
|
? setFieldValue(textValueAccessor, val)
|
||||||
? values[textValueAccessor].toString()
|
: null
|
||||||
: ''
|
}
|
||||||
}
|
/>
|
||||||
onRadioChange={(type) =>
|
) : (
|
||||||
setFieldValue('extractionType', type)
|
<SimpleRadio
|
||||||
}
|
key={title}
|
||||||
onTextChange={(val) =>
|
onChange={() => setFieldValue('extractionType', type)}
|
||||||
setFieldValue(textValueAccessor ?? '', val)
|
fieldName={'extractionType'}
|
||||||
}
|
value={values.extractionType}
|
||||||
/>
|
description={description}
|
||||||
) : (
|
title={title}
|
||||||
<SimpleRadio
|
/>
|
||||||
key={title}
|
)
|
||||||
onChange={() =>
|
)
|
||||||
setFieldValue('extractionType', type)
|
},
|
||||||
}
|
{
|
||||||
fieldName={'extractionType'}
|
title: 'Running Sum',
|
||||||
value={values.extractionType}
|
component: (
|
||||||
description={description}
|
<CheckboxWithDesc
|
||||||
title={title}
|
title={'Print Running Sum'}
|
||||||
/>
|
description={"Display the sum as it's calculated step by step."}
|
||||||
)
|
checked={values.printRunningSum}
|
||||||
)
|
onChange={(value) => setFieldValue('printRunningSum', value)}
|
||||||
},
|
|
||||||
{
|
|
||||||
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>
|
compute={(optionsValues, input) => {
|
||||||
|
const { extractionType, printRunningSum, separator } = optionsValues;
|
||||||
|
setResult(compute(input, extractionType, printRunningSum, separator));
|
||||||
|
}}
|
||||||
|
initialValues={initialValues}
|
||||||
|
input={input}
|
||||||
|
validationSchema={validationSchema}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,7 @@ import Info from './Info';
|
|||||||
import Separator from '../../../tools/Separator';
|
import Separator from '../../../tools/Separator';
|
||||||
import AllTools from '../../../components/allTools/AllTools';
|
import AllTools from '../../../components/allTools/AllTools';
|
||||||
import Examples from '../../../components/examples/Examples';
|
import Examples from '../../../components/examples/Examples';
|
||||||
|
import ColorSelector from '../../../components/options/ColorSelector';
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
joinCharacter: '',
|
joinCharacter: '',
|
||||||
@@ -115,23 +116,11 @@ s
|
|||||||
|
|
||||||
export default function JoinText() {
|
export default function JoinText() {
|
||||||
const [input, setInput] = useState<string>('');
|
const [input, setInput] = useState<string>('');
|
||||||
const { showSnackBar } = useContext(CustomSnackBarContext);
|
|
||||||
const [result, setResult] = useState<string>('');
|
const [result, setResult] = useState<string>('');
|
||||||
|
|
||||||
const FormikListenerComponent = ({ input }: { input: string }) => {
|
const compute = (optionsValues: typeof initialValues, input: any) => {
|
||||||
const { values } = useFormikContext<typeof initialValues>();
|
const { joinCharacter, deleteBlank, deleteTrailing } = optionsValues;
|
||||||
const { joinCharacter, deleteBlank, deleteTrailing } = values;
|
setResult(mergeText(input, deleteBlank, deleteTrailing, joinCharacter));
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
try {
|
|
||||||
setResult(mergeText(input, deleteBlank, deleteTrailing, joinCharacter));
|
|
||||||
} catch (exception: unknown) {
|
|
||||||
if (exception instanceof Error)
|
|
||||||
showSnackBar(exception.message, 'error');
|
|
||||||
}
|
|
||||||
}, [values, input]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function changeInputResult(input: string, result: string) {
|
function changeInputResult(input: string, result: string) {
|
||||||
@@ -156,50 +145,39 @@ export default function JoinText() {
|
|||||||
}
|
}
|
||||||
result={<ToolTextResult title={'Joined Text'} value={result} />}
|
result={<ToolTextResult title={'Joined Text'} value={result} />}
|
||||||
/>
|
/>
|
||||||
<ToolOptions>
|
<ToolOptions
|
||||||
<Formik
|
compute={compute}
|
||||||
initialValues={initialValues}
|
getGroups={({ values, setFieldValue }) => [
|
||||||
validationSchema={validationSchema}
|
{
|
||||||
onSubmit={() => {}}
|
title: 'Text Merged Options',
|
||||||
>
|
component: (
|
||||||
{({ setFieldValue, values }) => (
|
<TextFieldWithDesc
|
||||||
<Stack direction={'row'} spacing={2}>
|
placeholder={mergeOptions.placeholder}
|
||||||
<FormikListenerComponent input={input} />
|
value={values['joinCharacter']}
|
||||||
<ToolOptionGroups
|
onChange={(value) =>
|
||||||
groups={[
|
setFieldValue(mergeOptions.accessor, value)
|
||||||
{
|
}
|
||||||
title: 'Text Merged Options',
|
description={mergeOptions.description}
|
||||||
component: (
|
|
||||||
<TextFieldWithDesc
|
|
||||||
placeholder={mergeOptions.placeholder}
|
|
||||||
value={values['joinCharacter']}
|
|
||||||
onChange={(value) =>
|
|
||||||
setFieldValue(mergeOptions.accessor, value)
|
|
||||||
}
|
|
||||||
description={mergeOptions.description}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Blank Lines and Trailing Spaces',
|
|
||||||
component: blankTrailingOptions.map((option) => (
|
|
||||||
<CheckboxWithDesc
|
|
||||||
key={option.accessor}
|
|
||||||
title={option.title}
|
|
||||||
checked={!!values[option.accessor]}
|
|
||||||
onChange={(value) =>
|
|
||||||
setFieldValue(option.accessor, value)
|
|
||||||
}
|
|
||||||
description={option.description}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</Stack>
|
)
|
||||||
)}
|
},
|
||||||
</Formik>
|
{
|
||||||
</ToolOptions>
|
title: 'Blank Lines and Trailing Spaces',
|
||||||
|
component: blankTrailingOptions.map((option) => (
|
||||||
|
<CheckboxWithDesc
|
||||||
|
key={option.accessor}
|
||||||
|
title={option.title}
|
||||||
|
checked={!!values[option.accessor]}
|
||||||
|
onChange={(value) => setFieldValue(option.accessor, value)}
|
||||||
|
description={option.description}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
initialValues={initialValues}
|
||||||
|
input={input}
|
||||||
|
validationSchema={validationSchema}
|
||||||
|
/>
|
||||||
<Info
|
<Info
|
||||||
title="What Is a Text Joiner?"
|
title="What Is a Text Joiner?"
|
||||||
description="With this tool you can join parts of the text together. It takes a list of text values, separated by newlines, and merges them together. You can set the character that will be placed between the parts of the combined text. Also, you can ignore all empty lines and remove spaces and tabs at the end of all lines. Textabulous!"
|
description="With this tool you can join parts of the text together. It takes a list of text values, separated by newlines, and merges them together. You can set the character that will be placed between the parts of the combined text. Also, you can ignore all empty lines and remove spaces and tabs at the end of all lines. Textabulous!"
|
||||||
|
@@ -12,6 +12,7 @@ import RadioWithTextField from '../../../components/options/RadioWithTextField';
|
|||||||
import TextFieldWithDesc from '../../../components/options/TextFieldWithDesc';
|
import TextFieldWithDesc from '../../../components/options/TextFieldWithDesc';
|
||||||
import ToolOptionGroups from '../../../components/options/ToolOptionGroups';
|
import ToolOptionGroups from '../../../components/options/ToolOptionGroups';
|
||||||
import ToolInputAndResult from '../../../components/ToolInputAndResult';
|
import ToolInputAndResult from '../../../components/ToolInputAndResult';
|
||||||
|
import CheckboxWithDesc from '../../../components/options/CheckboxWithDesc';
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
splitSeparatorType: 'symbol' as SplitOperatorType,
|
splitSeparatorType: 'symbol' as SplitOperatorType,
|
||||||
@@ -82,44 +83,31 @@ export default function SplitText() {
|
|||||||
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 formRef = useRef<FormikProps<typeof initialValues>>(null);
|
||||||
const { showSnackBar } = useContext(CustomSnackBarContext);
|
const computeExternal = (optionsValues: typeof initialValues, input: any) => {
|
||||||
|
const {
|
||||||
|
splitSeparatorType,
|
||||||
|
outputSeparator,
|
||||||
|
charBeforeChunk,
|
||||||
|
charAfterChunk,
|
||||||
|
chunksValue,
|
||||||
|
symbolValue,
|
||||||
|
regexValue,
|
||||||
|
lengthValue
|
||||||
|
} = optionsValues;
|
||||||
|
|
||||||
const FormikListenerComponent = () => {
|
setResult(
|
||||||
const { values } = useFormikContext<typeof initialValues>();
|
compute(
|
||||||
|
splitSeparatorType,
|
||||||
useEffect(() => {
|
input,
|
||||||
try {
|
symbolValue,
|
||||||
const {
|
regexValue,
|
||||||
splitSeparatorType,
|
Number(lengthValue),
|
||||||
outputSeparator,
|
Number(chunksValue),
|
||||||
charBeforeChunk,
|
charBeforeChunk,
|
||||||
charAfterChunk,
|
charAfterChunk,
|
||||||
chunksValue,
|
outputSeparator
|
||||||
symbolValue,
|
)
|
||||||
regexValue,
|
);
|
||||||
lengthValue
|
|
||||||
} = values;
|
|
||||||
|
|
||||||
setResult(
|
|
||||||
compute(
|
|
||||||
splitSeparatorType,
|
|
||||||
input,
|
|
||||||
symbolValue,
|
|
||||||
regexValue,
|
|
||||||
Number(lengthValue),
|
|
||||||
Number(chunksValue),
|
|
||||||
charBeforeChunk,
|
|
||||||
charAfterChunk,
|
|
||||||
outputSeparator
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} 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({
|
const validationSchema = Yup.object({
|
||||||
// splitSeparator: Yup.string().required('The separator is required')
|
// splitSeparator: Yup.string().required('The separator is required')
|
||||||
@@ -131,57 +119,42 @@ export default function SplitText() {
|
|||||||
input={<ToolTextInput value={input} onChange={setInput} />}
|
input={<ToolTextInput value={input} onChange={setInput} />}
|
||||||
result={<ToolTextResult title={'Text pieces'} value={result} />}
|
result={<ToolTextResult title={'Text pieces'} value={result} />}
|
||||||
/>
|
/>
|
||||||
<ToolOptions>
|
<ToolOptions
|
||||||
<Formik
|
compute={computeExternal}
|
||||||
initialValues={initialValues}
|
getGroups={({ values, setFieldValue }) => [
|
||||||
validationSchema={validationSchema}
|
{
|
||||||
onSubmit={() => {}}
|
title: 'Split separator options',
|
||||||
>
|
component: splitOperators.map(({ title, description, type }) => (
|
||||||
{({ setFieldValue, values }) => (
|
<RadioWithTextField
|
||||||
<Stack direction={'row'} spacing={2}>
|
key={type}
|
||||||
<FormikListenerComponent />
|
radioValue={type}
|
||||||
<ToolOptionGroups
|
title={title}
|
||||||
groups={[
|
fieldName={'splitSeparatorType'}
|
||||||
{
|
description={description}
|
||||||
title: 'Split separator options',
|
value={values[`${type}Value`]}
|
||||||
component: splitOperators.map(
|
onRadioChange={(type) =>
|
||||||
({ title, description, type }) => (
|
setFieldValue('splitSeparatorType', type)
|
||||||
<RadioWithTextField
|
}
|
||||||
key={type}
|
onTextChange={(val) => setFieldValue(`${type}Value`, val)}
|
||||||
radioValue={type}
|
|
||||||
title={title}
|
|
||||||
fieldName={'splitSeparatorType'}
|
|
||||||
description={description}
|
|
||||||
value={values[`${type}Value`]}
|
|
||||||
onRadioChange={(type) =>
|
|
||||||
setFieldValue('splitSeparatorType', type)
|
|
||||||
}
|
|
||||||
onTextChange={(val) =>
|
|
||||||
setFieldValue(`${type}Value`, val)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Output separator options',
|
|
||||||
component: outputOptions.map((option) => (
|
|
||||||
<TextFieldWithDesc
|
|
||||||
key={option.accessor}
|
|
||||||
value={values[option.accessor]}
|
|
||||||
onChange={(value) =>
|
|
||||||
setFieldValue(option.accessor, value)
|
|
||||||
}
|
|
||||||
description={option.description}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</Stack>
|
))
|
||||||
)}
|
},
|
||||||
</Formik>
|
{
|
||||||
</ToolOptions>
|
title: 'Output separator options',
|
||||||
|
component: outputOptions.map((option) => (
|
||||||
|
<TextFieldWithDesc
|
||||||
|
key={option.accessor}
|
||||||
|
value={values[option.accessor]}
|
||||||
|
onChange={(value) => setFieldValue(option.accessor, value)}
|
||||||
|
description={option.description}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
initialValues={initialValues}
|
||||||
|
input={input}
|
||||||
|
validationSchema={validationSchema}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,11 @@
|
|||||||
import { Box, Stack } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import Grid from '@mui/material/Grid';
|
import React, { useState } from 'react';
|
||||||
import React, { useContext, useEffect, 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 { Formik, useFormikContext } from 'formik';
|
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import ToolOptions from '../../../components/options/ToolOptions';
|
import ToolOptions from '../../../components/options/ToolOptions';
|
||||||
import { compute } from './service';
|
import { compute } from './service';
|
||||||
import { CustomSnackBarContext } from '../../../contexts/CustomSnackBarContext';
|
|
||||||
import TextFieldWithDesc from '../../../components/options/TextFieldWithDesc';
|
import TextFieldWithDesc from '../../../components/options/TextFieldWithDesc';
|
||||||
import ToolOptionGroups from '../../../components/options/ToolOptionGroups';
|
|
||||||
import ToolInputAndResult from '../../../components/ToolInputAndResult';
|
import ToolInputAndResult from '../../../components/ToolInputAndResult';
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
@@ -21,23 +17,9 @@ export default function ToMorse() {
|
|||||||
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 formRef = useRef<FormikProps<typeof initialValues>>(null);
|
||||||
const { showSnackBar } = useContext(CustomSnackBarContext);
|
const computeOptions = (optionsValues: typeof initialValues, input: any) => {
|
||||||
|
const { dotSymbol, dashSymbol } = optionsValues;
|
||||||
const FormikListenerComponent = () => {
|
setResult(compute(input, dotSymbol, dashSymbol));
|
||||||
const { values } = useFormikContext<typeof initialValues>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
try {
|
|
||||||
const { dotSymbol, dashSymbol } = values;
|
|
||||||
|
|
||||||
setResult(compute(input, dotSymbol, dashSymbol));
|
|
||||||
} catch (exception: unknown) {
|
|
||||||
if (exception instanceof Error)
|
|
||||||
showSnackBar(exception.message, 'error');
|
|
||||||
}
|
|
||||||
}, [values, input]);
|
|
||||||
|
|
||||||
return null; // This component doesn't render anything
|
|
||||||
};
|
};
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
// splitSeparator: Yup.string().required('The separator is required')
|
// splitSeparator: Yup.string().required('The separator is required')
|
||||||
@@ -49,47 +31,38 @@ export default function ToMorse() {
|
|||||||
input={<ToolTextInput value={input} onChange={setInput} />}
|
input={<ToolTextInput value={input} onChange={setInput} />}
|
||||||
result={<ToolTextResult title={'Morse code'} value={result} />}
|
result={<ToolTextResult title={'Morse code'} value={result} />}
|
||||||
/>
|
/>
|
||||||
<ToolOptions>
|
<ToolOptions
|
||||||
<Formik
|
compute={computeOptions}
|
||||||
initialValues={initialValues}
|
getGroups={({ values, setFieldValue }) => [
|
||||||
validationSchema={validationSchema}
|
{
|
||||||
onSubmit={() => {}}
|
title: 'Short Signal',
|
||||||
>
|
component: (
|
||||||
{({ setFieldValue, values }) => (
|
<TextFieldWithDesc
|
||||||
<Stack direction={'row'} spacing={2}>
|
description={
|
||||||
<FormikListenerComponent />
|
'Symbol that will correspond to the dot in Morse code.'
|
||||||
<ToolOptionGroups
|
}
|
||||||
groups={[
|
value={values.dotSymbol}
|
||||||
{
|
onChange={(val) => setFieldValue('dotSymbol', val)}
|
||||||
title: 'Short Signal',
|
|
||||||
component: (
|
|
||||||
<TextFieldWithDesc
|
|
||||||
description={
|
|
||||||
'Symbol that will correspond to the dot in Morse code.'
|
|
||||||
}
|
|
||||||
value={values.dotSymbol}
|
|
||||||
onChange={(val) => setFieldValue('dotSymbol', val)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Long Signal',
|
|
||||||
component: (
|
|
||||||
<TextFieldWithDesc
|
|
||||||
description={
|
|
||||||
'Symbol that will correspond to the dash in Morse code.'
|
|
||||||
}
|
|
||||||
value={values.dashSymbol}
|
|
||||||
onChange={(val) => setFieldValue('dashSymbol', val)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</Stack>
|
)
|
||||||
)}
|
},
|
||||||
</Formik>
|
{
|
||||||
</ToolOptions>
|
title: 'Long Signal',
|
||||||
|
component: (
|
||||||
|
<TextFieldWithDesc
|
||||||
|
description={
|
||||||
|
'Symbol that will correspond to the dash in Morse code.'
|
||||||
|
}
|
||||||
|
value={values.dashSymbol}
|
||||||
|
onChange={(val) => setFieldValue('dashSymbol', val)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
initialValues={initialValues}
|
||||||
|
input={input}
|
||||||
|
validationSchema={validationSchema}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user