Merge pull request #26 from iib0011/examples

Examples
This commit is contained in:
Ibrahima G. Coulibaly 2025-02-27 02:23:10 +00:00 committed by GitHub
commit f855d33928
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 364 additions and 330 deletions

151
.idea/workspace.xml generated
View file

@ -4,10 +4,13 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="docs: readme">
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="refact: examples">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" 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/ToolHeader.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/ToolHeader.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/examples/Examples.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/examples/ToolExamples.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/join/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/join/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/tools/defineTool.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/tools/defineTool.tsx" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -23,7 +26,7 @@
<component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY">
<map>
<entry key="$PROJECT_DIR$" value="4-convert-jpg-to-png" />
<entry key="$PROJECT_DIR$" value="main" />
</map>
</option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
@ -59,47 +62,47 @@
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;ASKED_ADD_EXTERNAL_FILES&quot;: &quot;true&quot;,
&quot;ASKED_SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;,
&quot;Docker.Dockerfile build.executor&quot;: &quot;Run&quot;,
&quot;Docker.Dockerfile.executor&quot;: &quot;Run&quot;,
&quot;Playwright.JoinText Component.executor&quot;: &quot;Run&quot;,
&quot;Playwright.JoinText Component.should merge text pieces with specified join character.executor&quot;: &quot;Run&quot;,
&quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;Vitest.compute function (1).executor&quot;: &quot;Run&quot;,
&quot;Vitest.compute function.executor&quot;: &quot;Run&quot;,
&quot;Vitest.mergeText.executor&quot;: &quot;Run&quot;,
&quot;Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor&quot;: &quot;Run&quot;,
&quot;Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor&quot;: &quot;Run&quot;,
&quot;git-widget-placeholder&quot;: &quot;main&quot;,
&quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;C:/Users/Ibrahima/IdeaProjects/omni-tools/src/assets&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;npm.dev.executor&quot;: &quot;Run&quot;,
&quot;npm.lint.executor&quot;: &quot;Run&quot;,
&quot;npm.prebuild.executor&quot;: &quot;Run&quot;,
&quot;npm.script:create:tool.executor&quot;: &quot;Run&quot;,
&quot;npm.test.executor&quot;: &quot;Run&quot;,
&quot;npm.test:e2e.executor&quot;: &quot;Run&quot;,
&quot;npm.test:e2e:run.executor&quot;: &quot;Run&quot;,
&quot;prettierjs.PrettierConfiguration.Package&quot;: &quot;C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier&quot;,
&quot;project.structure.last.edited&quot;: &quot;Problems&quot;,
&quot;project.structure.proportion&quot;: &quot;0.0&quot;,
&quot;project.structure.side.proportion&quot;: &quot;0.2&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;settings.typescriptcompiler&quot;,
&quot;ts.external.directory.path&quot;: &quot;C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
<component name="PropertiesComponent"><![CDATA[{
"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": "examples",
"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"
}
}</component>
}]]></component>
<component name="ReactDesignerToolWindowState">
<option name="myId2Visible">
<map>
@ -262,31 +265,7 @@
<workItem from="1740490890760" duration="1889000" />
<workItem from="1740503199053" duration="4853000" />
<workItem from="1740584243965" duration="17000" />
<workItem from="1740613094492" duration="1804000" />
</task>
<task id="LOCAL-00077" summary="ci: run e2e tests">
<option name="closed" value="true" />
<created>1719587132558</created>
<option name="number" value="00077" />
<option name="presentableId" value="LOCAL-00077" />
<option name="project" value="LOCAL" />
<updated>1719587132558</updated>
</task>
<task id="LOCAL-00078" summary="ci: run e2e tests">
<option name="closed" value="true" />
<created>1719587281298</created>
<option name="number" value="00078" />
<option name="presentableId" value="LOCAL-00078" />
<option name="project" value="LOCAL" />
<updated>1719587281298</updated>
</task>
<task id="LOCAL-00079" summary="fix: ci">
<option name="closed" value="true" />
<created>1719588326608</created>
<option name="number" value="00079" />
<option name="presentableId" value="LOCAL-00079" />
<option name="project" value="LOCAL" />
<updated>1719588326608</updated>
<workItem from="1740613094492" duration="9615000" />
</task>
<task id="LOCAL-00080" summary="fix: ci">
<option name="closed" value="true" />
@ -656,7 +635,31 @@
<option name="project" value="LOCAL" />
<updated>1740614185980</updated>
</task>
<option name="localTasksCounter" value="126" />
<task id="LOCAL-00126" summary="chore: handle enter press on search">
<option name="closed" value="true" />
<created>1740614957672</created>
<option name="number" value="00126" />
<option name="presentableId" value="LOCAL-00126" />
<option name="project" value="LOCAL" />
<updated>1740614957672</updated>
</task>
<task id="LOCAL-00127" summary="chore: show tooloptions in example">
<option name="closed" value="true" />
<created>1740619610168</created>
<option name="number" value="00127" />
<option name="presentableId" value="LOCAL-00127" />
<option name="project" value="LOCAL" />
<updated>1740619610169</updated>
</task>
<task id="LOCAL-00128" summary="refact: examples">
<option name="closed" value="true" />
<created>1740620866551</created>
<option name="number" value="00128" />
<option name="presentableId" value="LOCAL-00128" />
<option name="project" value="LOCAL" />
<updated>1740620866551</updated>
</task>
<option name="localTasksCounter" value="129" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@ -688,9 +691,6 @@
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
<option name="CHECK_NEW_TODO" value="false" />
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
<MESSAGE value="feat: group list ui" />
<MESSAGE value="feat: reverse list ui" />
<MESSAGE value="feat: self host" />
<MESSAGE value="chore: format number" />
<MESSAGE value="feat: rotate ui" />
<MESSAGE value="feat: shuffle ui" />
@ -713,7 +713,10 @@
<MESSAGE value="docs: img" />
<MESSAGE value="fix: bg" />
<MESSAGE value="docs: readme" />
<option name="LAST_COMMIT_MESSAGE" value="docs: readme" />
<MESSAGE value="chore: handle enter press on search" />
<MESSAGE value="chore: show tooloptions in example" />
<MESSAGE value="refact: examples" />
<option name="LAST_COMMIT_MESSAGE" value="refact: examples" />
</component>
<component name="XSLT-Support.FileAssociations.UIState">
<expand />

View file

@ -26,7 +26,7 @@ function ToolLinks() {
return (
<Grid container spacing={2} mt={1}>
<Grid item md={12} lg={4}>
<Grid item md={12} lg={6}>
<StyledButton
sx={{ backgroundColor: 'white' }}
fullWidth
@ -36,16 +36,16 @@ function ToolLinks() {
Use This Tool
</StyledButton>
</Grid>
<Grid item md={12} lg={4}>
<Grid item md={12} lg={6}>
<StyledButton fullWidth variant="outlined" href="#examples">
See Examples
</StyledButton>
</Grid>
<Grid item md={12} lg={4}>
<StyledButton fullWidth variant="outlined" href="#tour">
Learn How to Use
</StyledButton>
</Grid>
{/*<Grid item md={12} lg={4}>*/}
{/* <StyledButton fullWidth variant="outlined" href="#tour">*/}
{/* Learn How to Use*/}
{/* </StyledButton>*/}
{/*</Grid>*/}
</Grid>
);
}

View file

@ -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<T> {
title: string;
description: string;
sampleText: string;
sampleResult: string;
sampleOptions: T;
changeInputResult: (newOptions: T) => void;
getGroups: GetGroupsType<T>;
}
export default function ExampleCard<T>({
title,
description,
sampleText,
sampleResult,
requiredOptions,
changeInputResult
}: ExampleCardProps) {
sampleOptions,
changeInputResult,
getGroups
}: ExampleCardProps<T>) {
const theme = useTheme();
return (
<Card
raised
onClick={() => {
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({
</Typography>
<Box
onClick={() => 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({
<ArrowDownwardIcon />
<Box
onClick={() => changeInputResult(sampleText, sampleResult)}
sx={{
display: 'flex',
zIndex: '2',
@ -106,7 +118,7 @@ export default function ExampleCard({
/>
</Box>
<RequiredOptions options={requiredOptions} />
<ExampleOptions options={sampleOptions} getGroups={getGroups} />
</Stack>
</CardContent>
</Card>

View file

@ -0,0 +1,19 @@
import ToolOptionGroups from '@components/options/ToolOptionGroups';
import { GetGroupsType } from '@components/options/ToolOptions';
import React from 'react';
export default function ExampleOptions<T>({
options,
getGroups
}: {
options: T;
getGroups: GetGroupsType<T>;
}) {
return (
<ToolOptionGroups
// @ts-ignore
groups={getGroups({ values: options })}
vertical
/>
);
}

View file

@ -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 (
<Box id={'examples'} mt={4}>
<Box mt={4} display="flex" gap={1} alignItems="center">
<Typography mb={2} fontSize={30} color={'primary'}>
{title}
</Typography>
<Typography mb={2} fontSize={30} color={'secondary'}>
{subtitle}
</Typography>
</Box>
<Stack direction={'row'} alignItems={'center'} spacing={2}>
<Grid container spacing={2}>
{exampleCards.map((card, index) => (
<Grid item xs={12} md={6} lg={4} key={index}>
<ExampleCard
title={card.title}
description={card.description}
sampleText={card.sampleText}
sampleResult={card.sampleResult}
requiredOptions={card.requiredOptions}
changeInputResult={card.changeInputResult}
/>
</Grid>
))}
</Grid>
</Stack>
</Box>
);
}

View file

@ -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 (
<Stack direction={'column'} alignItems={'left'} spacing={2}>
<Typography variant="h5" component="h3" sx={{ marginTop: '5px' }}>
Required options
</Typography>
<Typography variant="body2" component="p">
These options will be used automatically if you select this example.
</Typography>
<Box
onClick={handleBoxClick}
sx={{
zIndex: '2',
cursor: 'pointer',
bgcolor: 'transparent',
width: '100%',
height: '100%',
display: 'flex'
}}
>
<TextField
disabled
value={joinCharacter}
fullWidth
rows={1}
sx={{
'& .MuiOutlinedInput-root': {
zIndex: '-1'
}
}}
/>
</Box>
{deleteBlankLines ? (
<Box onClick={handleBoxClick}>
<CheckboxWithDesc
title="Delete Blank Lines"
checked={deleteBlankLines}
onChange={() => {}}
description="Delete lines that don't have text symbols."
/>
</Box>
) : (
''
)}
{deleteTrailingSpaces ? (
<Box onClick={handleBoxClick}>
<CheckboxWithDesc
title="Delete Training Spaces"
checked={deleteTrailingSpaces}
onChange={() => {}}
description="Remove spaces and tabs at the end of the lines."
/>
</Box>
) : (
''
)}
</Stack>
);
}

View file

@ -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<T> = Omit<
ExampleCardProps<T>,
'getGroups' | 'changeInputResult'
>;
export interface ExampleProps<T> {
title: string;
subtitle?: string;
exampleCards: CardExampleType<T>[];
getGroups: GetGroupsType<T>;
formRef: React.RefObject<FormikProps<T>>;
}
export default function ToolExamples<T>({
title,
subtitle,
exampleCards,
getGroups,
formRef
}: ExampleProps<T>) {
function changeInputResult(newOptions: T) {
formRef.current?.setValues(newOptions);
const toolsElement = document.getElementById('tool');
if (toolsElement) {
toolsElement.scrollIntoView({ behavior: 'smooth' });
}
}
return (
<Box id={'examples'} mt={4}>
<Box mt={4} display="flex" gap={1} alignItems="center">
<Typography mb={2} fontSize={30} color={'primary'}>
{`${title} Examples`}
</Typography>
<Typography mb={2} fontSize={30} color={'secondary'}>
{subtitle ?? 'Click to try!'}
</Typography>
</Box>
<Stack direction={'row'} alignItems={'center'} spacing={2}>
<Grid container spacing={2}>
{exampleCards.map((card, index) => (
<Grid item xs={12} md={6} lg={4} key={index}>
<ExampleCard
title={card.title}
description={card.description}
sampleText={card.sampleText}
sampleResult={card.sampleResult}
sampleOptions={card.sampleOptions}
getGroups={getGroups}
changeInputResult={changeInputResult}
/>
</Grid>
))}
</Grid>
</Stack>
</Box>
);
}

View file

@ -8,14 +8,16 @@ export interface ToolOptionGroup {
}
export default function ToolOptionGroups({
groups
groups,
vertical
}: {
groups: ToolOptionGroup[];
vertical?: boolean;
}) {
return (
<Grid container spacing={2}>
{groups.map((group) => (
<Grid item xs={12} md={4} key={group.title}>
<Grid item xs={12} md={vertical ? 12 : 4} key={group.title}>
<Typography mb={1} fontSize={22}>
{group.title}
</Typography>

View file

@ -7,7 +7,7 @@ import ToolOptionGroups, { ToolOptionGroup } from './ToolOptionGroups';
import { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';
import * as Yup from 'yup';
type UpdateField<T> = <Y extends keyof T>(field: Y, value: T[Y]) => void;
export type UpdateField<T> = <Y extends keyof T>(field: Y, value: T[Y]) => void;
const FormikListenerComponent = <T,>({
initialValues,
@ -68,6 +68,10 @@ const ToolBody = <T,>({
</Stack>
);
};
export type GetGroupsType<T> = (
formikProps: FormikProps<T> & { updateField: UpdateField<T> }
) => ToolOptionGroup[];
export default function ToolOptions<T extends FormikValues>({
children,
initialValues,
@ -82,9 +86,7 @@ export default function ToolOptions<T extends FormikValues>({
validationSchema?: any | (() => any);
compute: (optionsValues: T, input: any) => void;
input?: any;
getGroups: (
formikProps: FormikProps<T> & { updateField: UpdateField<T> }
) => ToolOptionGroup[];
getGroups: GetGroupsType<T>;
formRef?: RefObject<FormikProps<T>>;
}) {
const theme = useTheme();

View file

@ -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<InitialValuesType>[] = [
{
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<string>('');
const [result, setResult] = useState<string>('');
const compute = (optionsValues: typeof initialValues, input: any) => {
const formRef = useRef<FormikProps<InitialValuesType>>(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<InitialValuesType> = ({
values,
updateField
}) => [
{
title: 'Text Merged Options',
component: (
<TextFieldWithDesc
placeholder={mergeOptions.placeholder}
value={values['joinCharacter']}
onOwnChange={(value) => updateField(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) => updateField(option.accessor, value)}
description={option.description}
/>
))
}
}
];
return (
<Box>
<ToolInputAndResult
@ -141,34 +163,9 @@ export default function JoinText() {
result={<ToolTextResult title={'Joined Text'} value={result} />}
/>
<ToolOptions
formRef={formRef}
compute={compute}
getGroups={({ values, updateField }) => [
{
title: 'Text Merged Options',
component: (
<TextFieldWithDesc
placeholder={mergeOptions.placeholder}
value={values['joinCharacter']}
onOwnChange={(value) =>
updateField(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) => 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!"
/>
<Separator backgroundColor="#5581b5" margin="50px" />
<Examples
title="Text Joiner Examples"
subtitle="Click to try!"
exampleCards={exampleCards.map((card) => ({
...card,
changeInputResult
}))}
<ToolExamples
title={title}
exampleCards={exampleCards}
getGroups={getGroups}
formRef={formRef}
/>
</Box>
);

View file

@ -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<typeof initialValues>[] = [
{
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<string>('');
const [result, setResult] = useState<string>('');
// const formRef = useRef<FormikProps<typeof initialValues>>(null);
const formRef = useRef<FormikProps<typeof initialValues>>(null);
const computeExternal = (optionsValues: typeof initialValues, input: any) => {
const {
splitSeparatorType,
@ -104,6 +163,37 @@ export default function SplitText() {
);
};
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 (
<Box>
<ToolInputAndResult
@ -112,37 +202,16 @@ export default function SplitText() {
/>
<ToolOptions
compute={computeExternal}
getGroups={({ 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}
/>
))
}
]}
getGroups={getGroups}
initialValues={initialValues}
input={input}
/>
<ToolExamples
title={title}
exampleCards={exampleCards}
getGroups={getGroups}
formRef={formRef}
/>
</Box>
);
}

View file

@ -4,7 +4,7 @@ import { IconifyIcon } from '@iconify/react';
interface ToolOptions {
path: string;
component: LazyExoticComponent<JSXElementConstructor<NonNullable<unknown>>>;
component: LazyExoticComponent<JSXElementConstructor<ToolComponentProps>>;
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}
>
<Component />
<Component title={name} />
</ToolLayout>
);
}