diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index e0222d2..eeee1ff 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,10 +4,13 @@
-
+
-
-
+
+
+
+
+
@@ -23,7 +26,7 @@
@@ -59,47 +62,47 @@
- {
- "keyToString": {
- "ASKED_ADD_EXTERNAL_FILES": "true",
- "ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
- "Docker.Dockerfile build.executor": "Run",
- "Docker.Dockerfile.executor": "Run",
- "Playwright.JoinText Component.executor": "Run",
- "Playwright.JoinText Component.should merge text pieces with specified join character.executor": "Run",
- "RunOnceActivity.OpenProjectViewOnStart": "true",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "RunOnceActivity.git.unshallow": "true",
- "Vitest.compute function (1).executor": "Run",
- "Vitest.compute function.executor": "Run",
- "Vitest.mergeText.executor": "Run",
- "Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor": "Run",
- "Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor": "Run",
- "git-widget-placeholder": "main",
- "ignore.virus.scanning.warn.message": "true",
- "kotlin-language-version-configured": "true",
- "last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/src/assets",
- "node.js.detected.package.eslint": "true",
- "node.js.detected.package.tslint": "true",
- "node.js.selected.package.eslint": "(autodetect)",
- "node.js.selected.package.tslint": "(autodetect)",
- "nodejs_package_manager_path": "npm",
- "npm.dev.executor": "Run",
- "npm.lint.executor": "Run",
- "npm.prebuild.executor": "Run",
- "npm.script:create:tool.executor": "Run",
- "npm.test.executor": "Run",
- "npm.test:e2e.executor": "Run",
- "npm.test:e2e:run.executor": "Run",
- "prettierjs.PrettierConfiguration.Package": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier",
- "project.structure.last.edited": "Problems",
- "project.structure.proportion": "0.0",
- "project.structure.side.proportion": "0.2",
- "settings.editor.selected.configurable": "settings.typescriptcompiler",
- "ts.external.directory.path": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib",
- "vue.rearranger.settings.migration": "true"
+
+}]]>
@@ -688,9 +691,6 @@
-
-
-
@@ -713,7 +713,10 @@
-
+
+
+
+
diff --git a/src/components/ToolHeader.tsx b/src/components/ToolHeader.tsx
index 585a551..84b0236 100644
--- a/src/components/ToolHeader.tsx
+++ b/src/components/ToolHeader.tsx
@@ -26,7 +26,7 @@ function ToolLinks() {
return (
-
+
-
+
See Examples
-
-
- Learn How to Use
-
-
+ {/**/}
+ {/* */}
+ {/* Learn How to Use*/}
+ {/* */}
+ {/**/}
);
}
diff --git a/src/components/examples/ExampleCard.tsx b/src/components/examples/ExampleCard.tsx
index 32126bc..bbcffad 100644
--- a/src/components/examples/ExampleCard.tsx
+++ b/src/components/examples/ExampleCard.tsx
@@ -1,4 +1,3 @@
-import { ExampleCardProps } from './Examples';
import {
Box,
Card,
@@ -9,26 +8,42 @@ import {
useTheme
} from '@mui/material';
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
-import RequiredOptions from './RequiredOptions';
+import ExampleOptions from './ExampleOptions';
+import { GetGroupsType } from '@components/options/ToolOptions';
-export default function ExampleCard({
+export interface ExampleCardProps {
+ title: string;
+ description: string;
+ sampleText: string;
+ sampleResult: string;
+ sampleOptions: T;
+ changeInputResult: (newOptions: T) => void;
+ getGroups: GetGroupsType;
+}
+
+export default function ExampleCard({
title,
description,
sampleText,
sampleResult,
- requiredOptions,
- changeInputResult
-}: ExampleCardProps) {
+ sampleOptions,
+ changeInputResult,
+ getGroups
+}: ExampleCardProps) {
const theme = useTheme();
return (
{
+ changeInputResult(sampleOptions);
+ }}
sx={{
bgcolor: theme.palette.background.default,
height: '100%',
overflow: 'hidden',
borderRadius: 2,
transition: 'background-color 0.3s ease',
+ cursor: 'pointer',
'&:hover': {
boxShadow: '12px 9px 11px 2px #b8b9be, -6px -6px 12px #fff'
}
@@ -46,7 +61,6 @@ export default function ExampleCard({
changeInputResult(sampleText, sampleResult)}
sx={{
display: 'flex',
zIndex: '2',
@@ -55,7 +69,6 @@ export default function ExampleCard({
bgcolor: 'transparent',
padding: '5px 10px',
borderRadius: '5px',
- cursor: 'pointer',
boxShadow: 'inset 2px 2px 5px #b8b9be, inset -3px -3px 7px #fff;'
}}
>
@@ -77,7 +90,6 @@ export default function ExampleCard({
changeInputResult(sampleText, sampleResult)}
sx={{
display: 'flex',
zIndex: '2',
@@ -106,7 +118,7 @@ export default function ExampleCard({
/>
-
+
diff --git a/src/components/examples/ExampleOptions.tsx b/src/components/examples/ExampleOptions.tsx
new file mode 100644
index 0000000..cac9c94
--- /dev/null
+++ b/src/components/examples/ExampleOptions.tsx
@@ -0,0 +1,19 @@
+import ToolOptionGroups from '@components/options/ToolOptionGroups';
+import { GetGroupsType } from '@components/options/ToolOptions';
+import React from 'react';
+
+export default function ExampleOptions({
+ options,
+ getGroups
+}: {
+ options: T;
+ getGroups: GetGroupsType;
+}) {
+ return (
+
+ );
+}
diff --git a/src/components/examples/Examples.tsx b/src/components/examples/Examples.tsx
deleted file mode 100644
index d534b2e..0000000
--- a/src/components/examples/Examples.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { Box, Grid, Stack, Typography } from '@mui/material';
-import ExampleCard from './ExampleCard';
-
-export interface ExampleCardProps {
- title: string;
- description: string;
- sampleText: string;
- sampleResult: string;
- requiredOptions: RequiredOptionsProps;
- changeInputResult: (input: string, result: string) => void;
-}
-
-export interface RequiredOptionsProps {
- joinCharacter: string;
- deleteBlankLines: boolean;
- deleteTrailingSpaces: boolean;
-}
-
-interface ExampleProps {
- title: string;
- subtitle: string;
- exampleCards: ExampleCardProps[];
-}
-
-export default function Examples({
- title,
- subtitle,
- exampleCards
-}: ExampleProps) {
- return (
-
-
-
- {title}
-
-
- {subtitle}
-
-
-
-
-
- {exampleCards.map((card, index) => (
-
-
-
- ))}
-
-
-
- );
-}
diff --git a/src/components/examples/RequiredOptions.tsx b/src/components/examples/RequiredOptions.tsx
deleted file mode 100644
index d79a325..0000000
--- a/src/components/examples/RequiredOptions.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import { Box, Stack, TextField, Typography } from '@mui/material';
-import { RequiredOptionsProps } from './Examples';
-import CheckboxWithDesc from 'components/options/CheckboxWithDesc';
-
-export default function RequiredOptions({
- options
-}: {
- options: RequiredOptionsProps;
-}) {
- const { joinCharacter, deleteBlankLines, deleteTrailingSpaces } = options;
-
- const handleBoxClick = () => {
- const toolsElement = document.getElementById('tool');
- if (toolsElement) {
- toolsElement.scrollIntoView({ behavior: 'smooth' });
- }
- };
-
- return (
-
-
- Required options
-
-
- These options will be used automatically if you select this example.
-
-
-
-
-
-
- {deleteBlankLines ? (
-
- {}}
- description="Delete lines that don't have text symbols."
- />
-
- ) : (
- ''
- )}
- {deleteTrailingSpaces ? (
-
- {}}
- description="Remove spaces and tabs at the end of the lines."
- />
-
- ) : (
- ''
- )}
-
- );
-}
diff --git a/src/components/examples/ToolExamples.tsx b/src/components/examples/ToolExamples.tsx
new file mode 100644
index 0000000..c3cc481
--- /dev/null
+++ b/src/components/examples/ToolExamples.tsx
@@ -0,0 +1,65 @@
+import { Box, Grid, Stack, Typography } from '@mui/material';
+import ExampleCard, { ExampleCardProps } from './ExampleCard';
+import React from 'react';
+import { GetGroupsType } from '@components/options/ToolOptions';
+import { FormikProps } from 'formik';
+
+export type CardExampleType = Omit<
+ ExampleCardProps,
+ 'getGroups' | 'changeInputResult'
+>;
+
+export interface ExampleProps {
+ title: string;
+ subtitle?: string;
+ exampleCards: CardExampleType[];
+ getGroups: GetGroupsType;
+ formRef: React.RefObject>;
+}
+
+export default function ToolExamples({
+ title,
+ subtitle,
+ exampleCards,
+ getGroups,
+ formRef
+}: ExampleProps) {
+ function changeInputResult(newOptions: T) {
+ formRef.current?.setValues(newOptions);
+ const toolsElement = document.getElementById('tool');
+ if (toolsElement) {
+ toolsElement.scrollIntoView({ behavior: 'smooth' });
+ }
+ }
+
+ return (
+
+
+
+ {`${title} Examples`}
+
+
+ {subtitle ?? 'Click to try!'}
+
+
+
+
+
+ {exampleCards.map((card, index) => (
+
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/components/options/ToolOptionGroups.tsx b/src/components/options/ToolOptionGroups.tsx
index 3f98bbd..d594a00 100644
--- a/src/components/options/ToolOptionGroups.tsx
+++ b/src/components/options/ToolOptionGroups.tsx
@@ -8,14 +8,16 @@ export interface ToolOptionGroup {
}
export default function ToolOptionGroups({
- groups
+ groups,
+ vertical
}: {
groups: ToolOptionGroup[];
+ vertical?: boolean;
}) {
return (
{groups.map((group) => (
-
+
{group.title}
diff --git a/src/components/options/ToolOptions.tsx b/src/components/options/ToolOptions.tsx
index 3407e8e..09cb689 100644
--- a/src/components/options/ToolOptions.tsx
+++ b/src/components/options/ToolOptions.tsx
@@ -7,7 +7,7 @@ import ToolOptionGroups, { ToolOptionGroup } from './ToolOptionGroups';
import { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';
import * as Yup from 'yup';
-type UpdateField = (field: Y, value: T[Y]) => void;
+export type UpdateField = (field: Y, value: T[Y]) => void;
const FormikListenerComponent = ({
initialValues,
@@ -68,6 +68,10 @@ const ToolBody = ({
);
};
+
+export type GetGroupsType = (
+ formikProps: FormikProps & { updateField: UpdateField }
+) => ToolOptionGroup[];
export default function ToolOptions({
children,
initialValues,
@@ -82,9 +86,7 @@ export default function ToolOptions({
validationSchema?: any | (() => any);
compute: (optionsValues: T, input: any) => void;
input?: any;
- getGroups: (
- formikProps: FormikProps & { updateField: UpdateField }
- ) => ToolOptionGroup[];
+ getGroups: GetGroupsType;
formRef?: RefObject>;
}) {
const theme = useTheme();
diff --git a/src/pages/tools/string/join/index.tsx b/src/pages/tools/string/join/index.tsx
index f2d6e6f..b697b6e 100644
--- a/src/pages/tools/string/join/index.tsx
+++ b/src/pages/tools/string/join/index.tsx
@@ -1,9 +1,9 @@
import { Box } from '@mui/material';
-import React, { useState } from 'react';
+import React, { useRef, useState } from 'react';
import * as Yup from 'yup';
import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult';
-import ToolOptions from '@components/options/ToolOptions';
+import ToolOptions, { GetGroupsType } from '@components/options/ToolOptions';
import { mergeText } from './service';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
@@ -11,14 +11,18 @@ import ToolInputAndResult from '@components/ToolInputAndResult';
import ToolInfo from '@components/ToolInfo';
import Separator from '@components/Separator';
-import Examples from '@components/examples/Examples';
+import ToolExamples, {
+ CardExampleType
+} from '@components/examples/ToolExamples';
+import { FormikProps } from 'formik';
+import { ToolComponentProps } from '@tools/defineTool';
const initialValues = {
joinCharacter: '',
deleteBlank: true,
deleteTrailing: true
};
-
+type InitialValuesType = typeof initialValues;
const validationSchema = Yup.object().shape({
joinCharacter: Yup.string().required('Join character is required'),
deleteBlank: Yup.boolean().required('Delete blank is required'),
@@ -29,13 +33,13 @@ const mergeOptions = {
placeholder: 'Join Character',
description:
'Symbol that connects broken\n' + 'pieces of text. (Space by default.)\n',
- accessor: 'joinCharacter' as keyof typeof initialValues
+ accessor: 'joinCharacter' as keyof InitialValuesType
};
const blankTrailingOptions: {
title: string;
description: string;
- accessor: keyof typeof initialValues;
+ accessor: keyof InitialValuesType;
}[] = [
{
title: 'Delete Blank Lines',
@@ -49,7 +53,7 @@ const blankTrailingOptions: {
}
];
-const exampleCards = [
+const exampleCards: CardExampleType[] = [
{
title: 'Merge a To-Do List',
description:
@@ -62,10 +66,10 @@ feed the cat
make dinner
build a rocket ship and fly away`,
sampleResult: `clean the house and go shopping and feed the cat and make dinner and build a rocket ship and fly away`,
- requiredOptions: {
+ sampleOptions: {
joinCharacter: 'and',
- deleteBlankLines: true,
- deleteTrailingSpaces: true
+ deleteBlank: true,
+ deleteTrailing: true
}
},
{
@@ -78,10 +82,10 @@ processor
mouse
keyboard`,
sampleResult: `computer, memory, processor, mouse, keyboard`,
- requiredOptions: {
+ sampleOptions: {
joinCharacter: ',',
- deleteBlankLines: false,
- deleteTrailingSpaces: false
+ deleteBlank: false,
+ deleteTrailing: false
}
},
{
@@ -101,33 +105,51 @@ u
s
!`,
sampleResult: `Textabulous!`,
- requiredOptions: {
+ sampleOptions: {
joinCharacter: '',
- deleteBlankLines: false,
- deleteTrailingSpaces: false
+ deleteBlank: false,
+ deleteTrailing: false
}
}
];
-export default function JoinText() {
+export default function JoinText({ title }: ToolComponentProps) {
const [input, setInput] = useState('');
const [result, setResult] = useState('');
-
- const compute = (optionsValues: typeof initialValues, input: any) => {
+ const formRef = useRef>(null);
+ const compute = (optionsValues: InitialValuesType, input: any) => {
const { joinCharacter, deleteBlank, deleteTrailing } = optionsValues;
setResult(mergeText(input, deleteBlank, deleteTrailing, joinCharacter));
};
- function changeInputResult(input: string, result: string) {
- setInput(input);
- setResult(result);
-
- const toolsElement = document.getElementById('tool');
- if (toolsElement) {
- toolsElement.scrollIntoView({ behavior: 'smooth' });
+ const getGroups: GetGroupsType = ({
+ values,
+ updateField
+ }) => [
+ {
+ title: 'Text Merged Options',
+ component: (
+ updateField(mergeOptions.accessor, value)}
+ description={mergeOptions.description}
+ />
+ )
+ },
+ {
+ title: 'Blank Lines and Trailing Spaces',
+ component: blankTrailingOptions.map((option) => (
+ updateField(option.accessor, value)}
+ description={option.description}
+ />
+ ))
}
- }
-
+ ];
return (
}
/>
[
- {
- title: 'Text Merged Options',
- component: (
-
- updateField(mergeOptions.accessor, value)
- }
- description={mergeOptions.description}
- />
- )
- },
- {
- title: 'Blank Lines and Trailing Spaces',
- component: blankTrailingOptions.map((option) => (
- updateField(option.accessor, value)}
- description={option.description}
- />
- ))
- }
- ]}
+ getGroups={getGroups}
initialValues={initialValues}
input={input}
/>
@@ -177,13 +174,11 @@ export default function JoinText() {
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!"
/>
- ({
- ...card,
- changeInputResult
- }))}
+
);
diff --git a/src/pages/tools/string/split/index.tsx b/src/pages/tools/string/split/index.tsx
index 1ec62e8..1fbd324 100644
--- a/src/pages/tools/string/split/index.tsx
+++ b/src/pages/tools/string/split/index.tsx
@@ -1,12 +1,17 @@
import { Box } from '@mui/material';
-import React, { useState } from 'react';
+import React, { useRef, useState } from 'react';
import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult';
-import ToolOptions from '@components/options/ToolOptions';
+import ToolOptions, { GetGroupsType } from '@components/options/ToolOptions';
import { compute, SplitOperatorType } from './service';
import RadioWithTextField from '@components/options/RadioWithTextField';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import ToolInputAndResult from '@components/ToolInputAndResult';
+import ToolExamples, {
+ CardExampleType
+} from '@components/examples/ToolExamples';
+import { ToolComponentProps } from '@tools/defineTool';
+import { FormikProps } from 'formik';
const initialValues = {
splitSeparatorType: 'symbol' as SplitOperatorType,
@@ -73,10 +78,64 @@ const outputOptions: {
}
];
-export default function SplitText() {
+const exampleCards: CardExampleType[] = [
+ {
+ title: 'Split German Numbers',
+ description:
+ 'In this example, we break the text into pieces by two characters – a comma and space. As a result, we get a column of numbers from 1 to 10 in German.',
+ sampleText: `1 - eins, 2 - zwei, 3 - drei, 4 - vier, 5 - fünf, 6 - sechs, 7 - sieben, 8 - acht, 9 - neun, 10 - zehn`,
+ sampleResult: `1 - eins
+2 - zwei
+3 - drei
+4 - vier
+5 - fünf
+6 - sechs
+7 - sieben
+8 - acht
+9 - neun
+10 - zehn`,
+ sampleOptions: {
+ ...initialValues,
+ symbolValue: ',',
+ splitSeparatorType: 'symbol',
+ outputSeparator: '\n'
+ }
+ },
+ {
+ title: 'Text Cleanup via a Regular Expression',
+ description:
+ 'In this example, we use a super smart regular expression trick to clean-up the text. This regexp finds all non-alphabetic characters and splits the text into pieces by these non-alphabetic chars. As a result, we extract only those parts of the text that contain Latin letters and words.',
+ sampleText: `Finding%№1.65*;?words()is'12#easy_`,
+ sampleResult: `Finding
+words
+is
+easy`,
+ sampleOptions: {
+ ...initialValues,
+ regexValue: '[^a-zA-Z]+',
+ splitSeparatorType: 'regex',
+ outputSeparator: '\n'
+ }
+ },
+ {
+ title: 'Three-dot Output Separator',
+ description:
+ 'This example splits the text by spaces and then places three dots between the words.',
+ sampleText: `If you started with $0.01 and doubled your money every day, it would take 27 days to become a millionaire.`,
+ sampleResult: `If...you...started...with...$0.01...and...doubled...your...money...every...day,...it...would...take...27...days...to...become...a...millionaire.!`,
+ sampleOptions: {
+ ...initialValues,
+ symbolValue: '',
+ splitSeparatorType: 'symbol',
+ outputSeparator: '...'
+ }
+ }
+];
+
+export default function SplitText({ title }: ToolComponentProps) {
const [input, setInput] = useState('');
const [result, setResult] = useState('');
- // const formRef = useRef>(null);
+ const formRef = useRef>(null);
const computeExternal = (optionsValues: typeof initialValues, input: any) => {
const {
splitSeparatorType,
@@ -104,6 +163,37 @@ export default function SplitText() {
);
};
+ const getGroups: GetGroupsType = ({
+ values,
+ updateField
+ }) => [
+ {
+ title: 'Split separator options',
+ component: splitOperators.map(({ title, description, type }) => (
+ updateField('splitSeparatorType', type)}
+ onTextChange={(val) => updateField(`${type}Value`, val)}
+ />
+ ))
+ },
+ {
+ title: 'Output separator options',
+ component: outputOptions.map((option) => (
+ updateField(option.accessor, value)}
+ description={option.description}
+ />
+ ))
+ }
+ ];
return (
[
- {
- title: 'Split separator options',
- component: splitOperators.map(({ title, description, type }) => (
- updateField('splitSeparatorType', type)}
- onTextChange={(val) => updateField(`${type}Value`, val)}
- />
- ))
- },
- {
- title: 'Output separator options',
- component: outputOptions.map((option) => (
- updateField(option.accessor, value)}
- description={option.description}
- />
- ))
- }
- ]}
+ getGroups={getGroups}
initialValues={initialValues}
input={input}
/>
+
);
}
diff --git a/src/tools/defineTool.tsx b/src/tools/defineTool.tsx
index 3c146cc..f9259f5 100644
--- a/src/tools/defineTool.tsx
+++ b/src/tools/defineTool.tsx
@@ -4,7 +4,7 @@ import { IconifyIcon } from '@iconify/react';
interface ToolOptions {
path: string;
- component: LazyExoticComponent>>;
+ component: LazyExoticComponent>;
keywords: string[];
icon?: IconifyIcon | string;
name: string;
@@ -25,6 +25,10 @@ export interface DefinedTool {
component: () => JSX.Element;
}
+export interface ToolComponentProps {
+ title?: any;
+}
+
export const defineTool = (
basePath: ToolCategory,
options: ToolOptions
@@ -55,7 +59,7 @@ export const defineTool = (
icon={icon}
type={basePath}
>
-
+
);
}