mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-11-09 09:55:07 +05:30
feat: Split string
This commit is contained in:
parent
93113b15eb
commit
19f0de8909
18 changed files with 305 additions and 20 deletions
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -12,6 +12,8 @@
|
|||
"@emotion/styled": "^11.11.5",
|
||||
"@mui/icons-material": "^5.15.20",
|
||||
"@mui/material": "^5.15.20",
|
||||
"@types/lodash": "^4.17.5",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.23.1"
|
||||
|
|
@ -1976,6 +1978,11 @@
|
|||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.5.tgz",
|
||||
"integrity": "sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw=="
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.14.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.6.tgz",
|
||||
|
|
@ -5149,8 +5156,7 @@
|
|||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@
|
|||
"@emotion/styled": "^11.11.5",
|
||||
"@mui/icons-material": "^5.15.20",
|
||||
"@mui/material": "^5.15.20",
|
||||
"@types/lodash": "^4.17.5",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.23.1"
|
||||
|
|
|
|||
BIN
src/assets/text.png
Normal file
BIN
src/assets/text.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
|
|
@ -1,6 +1,10 @@
|
|||
import {BrowserRouter, useRoutes} from "react-router-dom";
|
||||
import routesConfig from "../config/routesConfig";
|
||||
import Navbar from "./Navbar";
|
||||
import {Suspense} from "react";
|
||||
import Loading from "./Loading";
|
||||
import {ThemeProvider} from "@mui/material";
|
||||
import theme from "../config/muiConfig";
|
||||
|
||||
const AppRoutes = () => {
|
||||
return useRoutes(routesConfig);
|
||||
|
|
@ -9,10 +13,14 @@ const AppRoutes = () => {
|
|||
function App() {
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<BrowserRouter>
|
||||
<Navbar/>
|
||||
<Suspense fallback={<Loading/>}>
|
||||
<AppRoutes/>
|
||||
</Suspense>
|
||||
</BrowserRouter>
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
53
src/components/Loading.tsx
Normal file
53
src/components/Loading.tsx
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import Typography from "@mui/material/Typography";
|
||||
import {useState} from "react";
|
||||
import clsx from "clsx";
|
||||
import Box from "@mui/material/Box";
|
||||
import {useTimeout} from "../hooks";
|
||||
|
||||
export type FuseLoadingProps = {
|
||||
delay?: number;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* FuseLoading displays a loading state with an optional delay
|
||||
*/
|
||||
function FuseLoading(props: FuseLoadingProps) {
|
||||
const {delay = 0, className} = props;
|
||||
const [showLoading, setShowLoading] = useState(!delay);
|
||||
|
||||
useTimeout(() => {
|
||||
setShowLoading(true);
|
||||
}, delay);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
className,
|
||||
"flex flex-1 h-full w-full self-center flex-col items-center justify-center p-24",
|
||||
!showLoading ? "hidden" : "",
|
||||
)}
|
||||
>
|
||||
<Typography
|
||||
className="-mb-16 text-13 font-medium sm:text-20"
|
||||
color="text.secondary"
|
||||
>
|
||||
Chargement
|
||||
</Typography>
|
||||
<Box
|
||||
id="spinner"
|
||||
sx={{
|
||||
"& > div": {
|
||||
backgroundColor: "palette.secondary.main",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div className="bounce1"/>
|
||||
<div className="bounce2"/>
|
||||
<div className="bounce3"/>
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FuseLoading;
|
||||
18
src/components/ToolHeader.tsx
Normal file
18
src/components/ToolHeader.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import {Box, Stack} from "@mui/material";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import textImage from '../assets/text.png'
|
||||
|
||||
interface ToolHeaderProps {
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default function ToolHeader({title, description}: ToolHeaderProps) {
|
||||
return (<Stack direction={'row'} alignItems={'center'} spacing={2} mt={4}>
|
||||
<Box>
|
||||
<Typography mb={2} fontSize={30} color={'primary'}>{title}</Typography>
|
||||
<Typography fontSize={20}>{description}</Typography>
|
||||
</Box>
|
||||
<img width={'20%'} src={textImage}/>
|
||||
</Stack>)
|
||||
}
|
||||
7
src/components/ToolLayout.tsx
Normal file
7
src/components/ToolLayout.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import {Box} from "@mui/material";
|
||||
import {ReactNode} from "react";
|
||||
|
||||
export default function ToolLayout({children}: { children: ReactNode }) {
|
||||
return (<Box width={'100%'} display={'flex'} flexDirection={'column'} alignItems={'center'}><Box
|
||||
width={'85%'}>{children}</Box></Box>)
|
||||
}
|
||||
11
src/config/muiConfig.ts
Normal file
11
src/config/muiConfig.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import {createTheme} from "@mui/material";
|
||||
|
||||
const theme = createTheme({
|
||||
typography: {
|
||||
button: {
|
||||
textTransform: 'none'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default theme;
|
||||
|
|
@ -2,6 +2,7 @@ import {RouteObject} from "react-router-dom";
|
|||
import {Navigate} from "react-router-dom";
|
||||
import {ImagesConfig} from "../pages/images/ImagesConfig";
|
||||
import {lazy} from "react";
|
||||
import {StringConfig} from "../pages/string/StringConfig";
|
||||
|
||||
const Home = lazy(() => import("../pages/home"));
|
||||
|
||||
|
|
@ -14,6 +15,10 @@ const routes: RouteObject[] = [
|
|||
path: "images",
|
||||
children: ImagesConfig
|
||||
},
|
||||
{
|
||||
path: "string",
|
||||
children: StringConfig
|
||||
},
|
||||
{
|
||||
path: "*",
|
||||
element: <Navigate to="404"/>,
|
||||
|
|
|
|||
4
src/hooks/index.ts
Normal file
4
src/hooks/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export {default as useDebounce} from "./useDebounce";
|
||||
export {default as useTimeout} from "./useTimeout";
|
||||
export {default as usePrevious} from "./usePrevious";
|
||||
export {default as useUpdateEffect} from "./useUpdateEffect";
|
||||
38
src/hooks/useDebounce.ts
Normal file
38
src/hooks/useDebounce.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import {useCallback, useEffect, useRef} from "react";
|
||||
import _ from "lodash";
|
||||
|
||||
/**
|
||||
* Debounce hook.
|
||||
* @param {T} callback
|
||||
* @param {number} delay
|
||||
* @returns {T}
|
||||
*/
|
||||
function useDebounce<T extends (...args: never[]) => void>(
|
||||
callback: T,
|
||||
delay: number,
|
||||
): T {
|
||||
const callbackRef = useRef<T>(callback);
|
||||
|
||||
// Update the current callback each time it changes.
|
||||
useEffect(() => {
|
||||
callbackRef.current = callback;
|
||||
}, [callback]);
|
||||
|
||||
const debouncedFn = useCallback(
|
||||
_.debounce((...args: never[]) => {
|
||||
callbackRef.current(...args);
|
||||
}, delay),
|
||||
[delay],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Cleanup function to cancel any pending debounced calls
|
||||
return () => {
|
||||
debouncedFn.cancel();
|
||||
};
|
||||
}, [debouncedFn]);
|
||||
|
||||
return debouncedFn as unknown as T;
|
||||
}
|
||||
|
||||
export default useDebounce;
|
||||
19
src/hooks/usePrevious.ts
Normal file
19
src/hooks/usePrevious.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { useEffect, useRef } from "react";
|
||||
|
||||
/**
|
||||
* The usePrevious function is a custom hook that returns the previous value of a variable.
|
||||
* It takes in a value as a parameter and returns the previous value.
|
||||
*/
|
||||
function usePrevious<T>(value: T): T | undefined {
|
||||
const ref = useRef<T | undefined>();
|
||||
|
||||
// Store current value in ref
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
}, [value]);
|
||||
|
||||
// Return previous value (happens before update in useEffect above)
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
export default usePrevious;
|
||||
30
src/hooks/useTimeout.ts
Normal file
30
src/hooks/useTimeout.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { useEffect, useRef } from "react";
|
||||
|
||||
/**
|
||||
* The useTimeout function is a custom hook that sets a timeout for a given callback function.
|
||||
* It takes in a callback function and a delay time in milliseconds as parameters.
|
||||
* It returns nothing.
|
||||
*/
|
||||
function useTimeout(callback: () => void, delay: number) {
|
||||
const callbackRef = useRef(callback);
|
||||
|
||||
useEffect(() => {
|
||||
callbackRef.current = callback;
|
||||
}, [callback]);
|
||||
|
||||
useEffect(() => {
|
||||
let timer: NodeJS.Timeout | undefined;
|
||||
|
||||
if (delay !== null && callback && typeof callback === "function") {
|
||||
timer = setTimeout(callbackRef.current, delay);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
};
|
||||
}, [callback, delay]);
|
||||
}
|
||||
|
||||
export default useTimeout;
|
||||
19
src/hooks/useUpdateEffect.ts
Normal file
19
src/hooks/useUpdateEffect.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { DependencyList, EffectCallback, useEffect, useRef } from "react";
|
||||
|
||||
/**
|
||||
* The useUpdateEffect function is a custom hook that behaves like useEffect, but only runs on updates and not on initial mount.
|
||||
* It takes in an effect function and an optional dependency list as parameters.
|
||||
* It returns nothing.
|
||||
*/
|
||||
const useUpdateEffect = (effect: EffectCallback, deps?: DependencyList) => {
|
||||
const isInitialMount = useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialMount.current) {
|
||||
isInitialMount.current = false;
|
||||
}
|
||||
return effect();
|
||||
}, deps);
|
||||
};
|
||||
|
||||
export default useUpdateEffect;
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import {Box, Grid, Icon, Input, Stack, TextField} from "@mui/material";
|
||||
import {Box, Icon, Input, Stack, TextField} from "@mui/material";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import {useNavigate} from "react-router-dom";
|
||||
|
||||
export default function Home() {
|
||||
const exampleTools: { label: string; url: string }[] = [{
|
||||
label: 'Create a transparent image',
|
||||
url: ''
|
||||
|
|
@ -12,10 +13,13 @@ export default function Home() {
|
|||
{label: 'Pick a random item', url: ''},
|
||||
{label: 'Find and replace text', url: ''},
|
||||
{label: 'Convert emoji to image', url: ''},
|
||||
{label: 'Split a string', url: ''},
|
||||
{label: 'Split a string', url: '/string/split'},
|
||||
{label: 'Calculate number sum', url: ''},
|
||||
{label: 'Pixelate an image', url: ''},
|
||||
]
|
||||
export default function Home() {
|
||||
const navigate = useNavigate()
|
||||
|
||||
return (<Box padding={5} display={'flex'} flexDirection={'column'} alignItems={'center'} justifyContent={'center'}
|
||||
width={'100%'}>
|
||||
<Box width={"60%"}>
|
||||
|
|
@ -38,6 +42,7 @@ export default function Home() {
|
|||
<Grid container spacing={1} mt={2}>
|
||||
{exampleTools.map((tool) => (
|
||||
<Grid
|
||||
onClick={() => navigate(tool.url)}
|
||||
item
|
||||
xs={4}
|
||||
key={tool.label}
|
||||
|
|
|
|||
10
src/pages/string/StringConfig.tsx
Normal file
10
src/pages/string/StringConfig.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import {RouteObject} from "react-router-dom";
|
||||
import {lazy} from "react";
|
||||
|
||||
const StringHome = lazy(() => import("./index"));
|
||||
const StringSplit = lazy(() => import("./split"));
|
||||
|
||||
export const StringConfig: RouteObject[] = [
|
||||
{path: '', element: <StringHome/>},
|
||||
{path: 'split', element: <StringSplit/>},
|
||||
]
|
||||
5
src/pages/string/index.tsx
Normal file
5
src/pages/string/index.tsx
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import {Box} from "@mui/material";
|
||||
|
||||
export default function StringHome() {
|
||||
return (<Box></Box>)
|
||||
}
|
||||
45
src/pages/string/split/index.tsx
Normal file
45
src/pages/string/split/index.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import ToolHeader from "../../../components/ToolHeader";
|
||||
import ToolLayout from "../../../components/ToolLayout";
|
||||
import {Box, Stack, TextField} from "@mui/material";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
import PublishIcon from '@mui/icons-material/Publish';
|
||||
import ContentPasteIcon from '@mui/icons-material/ContentPaste';
|
||||
import DownloadIcon from '@mui/icons-material/Download';
|
||||
import React, {useEffect, useState} from "react";
|
||||
|
||||
export default function SplitText() {
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('')
|
||||
useEffect(() => {
|
||||
setResult(input.split(' ').join('\n'))
|
||||
}, [input]);
|
||||
return (
|
||||
<ToolLayout>
|
||||
<ToolHeader title={"Text Splitter"}
|
||||
description={"World's simplest browser-based utility for splitting text. Load your text in the input form on the left and you'll automatically get pieces of this text on the right. Powerful, free, and fast. Load text – get chunks."}/>
|
||||
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={6}>
|
||||
<Typography fontSize={30} color={'primary'}>Input text</Typography>
|
||||
<TextField value={input} onChange={event => setInput(event.target.value)} fullWidth multiline rows={10}/>
|
||||
<Stack mt={1} direction={'row'} spacing={2}>
|
||||
<Button startIcon={<PublishIcon/>}>Import from file</Button>
|
||||
<Button startIcon={<ContentPasteIcon/>}>Copy to clipboard</Button>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography fontSize={30} color={'primary'}>Text pieces</Typography>
|
||||
<TextField value={result} fullWidth multiline rows={10}/>
|
||||
<Stack mt={1} direction={'row'} spacing={2}>
|
||||
<Button startIcon={<DownloadIcon/>}>Save as</Button>
|
||||
<Button startIcon={<ContentPasteIcon/>}>Copy to clipboard</Button>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Box mt={2}>
|
||||
|
||||
</Box>
|
||||
</ToolLayout>)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue