mirror of
https://github.com/ershisan99/DevToysWeb.git
synced 2025-12-17 12:32:56 +00:00
feat: add searched tools page
This commit is contained in:
23
src/components/common/ToolCardGrid.tsx
Normal file
23
src/components/common/ToolCardGrid.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { Grid } from "@mui/material";
|
||||||
|
import equal from "fast-deep-equal";
|
||||||
|
import { ComponentPropsWithoutRef, memo } from "react";
|
||||||
|
|
||||||
|
import ToolCard from "./ToolCard";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
tools: ComponentPropsWithoutRef<typeof ToolCard>[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledComponent = ({ tools }: Props) => (
|
||||||
|
<Grid container rowSpacing={4} columnSpacing={2}>
|
||||||
|
{tools.map(({ icon, title, description, href, disabled }) => (
|
||||||
|
<Grid key={title} item>
|
||||||
|
<ToolCard {...{ icon, title, description, href, disabled }} />
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Component = memo(StyledComponent, equal);
|
||||||
|
|
||||||
|
export default Component;
|
||||||
@@ -3,5 +3,6 @@ import Configurations from "./Configurations";
|
|||||||
import Main from "./Main";
|
import Main from "./Main";
|
||||||
import MainItem from "./MainItem";
|
import MainItem from "./MainItem";
|
||||||
import TextField from "./TextField";
|
import TextField from "./TextField";
|
||||||
|
import ToolCardGrid from "./ToolCardGrid";
|
||||||
|
|
||||||
export { CodeEditorHalfLoading, Configurations, Main, MainItem, TextField };
|
export { CodeEditorHalfLoading, Configurations, Main, MainItem, TextField, ToolCardGrid };
|
||||||
|
|||||||
@@ -56,8 +56,11 @@ const Container = () => {
|
|||||||
const onChange: Props["onChange"] = useCallback(
|
const onChange: Props["onChange"] = useCallback(
|
||||||
({ currentTarget: { value } }) => {
|
({ currentTarget: { value } }) => {
|
||||||
setText(value);
|
setText(value);
|
||||||
|
|
||||||
|
const url = value === "" ? pagesPath.$url() : pagesPath.search.$url();
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
router.push(pagesPath.$url());
|
router.push(url);
|
||||||
},
|
},
|
||||||
[setText, router]
|
[setText, router]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,160 +1,18 @@
|
|||||||
import {
|
import { ComponentPropsWithoutRef, memo } from "react";
|
||||||
Code,
|
|
||||||
DataObject,
|
|
||||||
DragHandle,
|
|
||||||
Filter,
|
|
||||||
Fingerprint,
|
|
||||||
Key,
|
|
||||||
Link,
|
|
||||||
Numbers,
|
|
||||||
TextIncrease,
|
|
||||||
Transform,
|
|
||||||
} from "@mui/icons-material";
|
|
||||||
import { Grid } from "@mui/material";
|
|
||||||
import Fuse from "fuse.js";
|
|
||||||
import { memo } from "react";
|
|
||||||
import { selector, useRecoilValue } from "recoil";
|
|
||||||
|
|
||||||
import { Main } from "@/components/common";
|
import { Main, ToolCardGrid } from "@/components/common";
|
||||||
import { searchTextState } from "@/components/layout/states";
|
import { allTools } from "@/data/tools";
|
||||||
import { pagesPath } from "@/libs/$path";
|
|
||||||
|
|
||||||
import Card from "./Card";
|
type Props = ComponentPropsWithoutRef<typeof ToolCardGrid>;
|
||||||
|
|
||||||
type Props = {
|
const StyledComponent = ({ tools }: Props) => (
|
||||||
filteredTools: typeof tools;
|
|
||||||
};
|
|
||||||
|
|
||||||
const tools = [
|
|
||||||
{
|
|
||||||
icon: <Transform />,
|
|
||||||
title: "Json <> Yaml Converter",
|
|
||||||
description: "Convert Json data to Yaml and vice versa",
|
|
||||||
keywords: "json yaml converter",
|
|
||||||
href: pagesPath.converters.json_yaml.$url(),
|
|
||||||
disabled: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Numbers />,
|
|
||||||
title: "Number Base Converter",
|
|
||||||
description: "Convert numbers from one base to another",
|
|
||||||
keywords: "number base converter",
|
|
||||||
href: pagesPath.converters.number_base.$url(),
|
|
||||||
disabled: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Code />,
|
|
||||||
title: "HTML Encoder / Decoder",
|
|
||||||
description:
|
|
||||||
"Encode or decode all the applicable characters to their corresponding HTML entities",
|
|
||||||
keywords: "html encoder escaper decocder unescaper",
|
|
||||||
href: pagesPath.encoders_decoders.html.$url(),
|
|
||||||
disabled: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Link />,
|
|
||||||
title: "URL Encoder / Decoder",
|
|
||||||
description:
|
|
||||||
"Encode or decode all the applicable characters to their corresponding URL entities",
|
|
||||||
keywords: "url encoder escaper decocder unescaper",
|
|
||||||
href: pagesPath.$url(),
|
|
||||||
disabled: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <DragHandle />,
|
|
||||||
title: "Base 64 Encoder / Decoder",
|
|
||||||
description: "Encode and decode Base64 data",
|
|
||||||
keywords: "base64 encoder decocder",
|
|
||||||
href: pagesPath.$url(),
|
|
||||||
disabled: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Key />,
|
|
||||||
title: "JWT Decoder",
|
|
||||||
description: "Decode a JWT header, payload and signature",
|
|
||||||
keywords: "jwt json web token decocder",
|
|
||||||
href: pagesPath.$url(),
|
|
||||||
disabled: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <DataObject />,
|
|
||||||
title: "JSON Formatter",
|
|
||||||
description: "Indent or minify JSON data",
|
|
||||||
keywords: "json formatter",
|
|
||||||
href: pagesPath.$url(),
|
|
||||||
disabled: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Fingerprint />,
|
|
||||||
title: "Hash Generator",
|
|
||||||
description: "Calculate MD5, SHA1, SHA256 and SHA512 hash from text data",
|
|
||||||
keywords: "hash generator md5 sha1 sha256 sha512",
|
|
||||||
href: pagesPath.$url(),
|
|
||||||
disabled: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Numbers />,
|
|
||||||
title: "UUID Generator",
|
|
||||||
description: "Generate UUIDs version 1 and 4",
|
|
||||||
keywords: "guid uuid1 uuid4 generator",
|
|
||||||
href: pagesPath.$url(),
|
|
||||||
disabled: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <TextIncrease />,
|
|
||||||
title: "Regex Tester",
|
|
||||||
description: "Validate and test regular expressions",
|
|
||||||
keywords: "regular expression regex validator tester",
|
|
||||||
href: pagesPath.$url(),
|
|
||||||
disabled: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Filter />,
|
|
||||||
title: "PNG / JPEG Compressor",
|
|
||||||
description: "Lossless PNG and JPEG optimizer",
|
|
||||||
keywords: "png jpeg compressor optimizer image",
|
|
||||||
href: pagesPath.$url(),
|
|
||||||
disabled: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const StyledComponent = ({ filteredTools }: Props) => (
|
|
||||||
<Main title="All tools">
|
<Main title="All tools">
|
||||||
<Grid container rowSpacing={4} columnSpacing={2}>
|
<ToolCardGrid {...{ tools }} />
|
||||||
{filteredTools.map(({ icon, title, description, href, disabled }) => (
|
|
||||||
<Grid key={title} item>
|
|
||||||
<Card {...{ icon, title, description, href, disabled }} />
|
|
||||||
</Grid>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
</Main>
|
</Main>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const Component = memo(StyledComponent);
|
export const Component = memo(StyledComponent);
|
||||||
|
|
||||||
const filteredToolsState = selector({
|
const Container = () => <Component tools={allTools} />;
|
||||||
key: "filteredToolsState",
|
|
||||||
get: ({ get }) => {
|
|
||||||
const searchText = get(searchTextState).trim();
|
|
||||||
|
|
||||||
if (searchText === "") {
|
|
||||||
return { filteredTools: tools };
|
|
||||||
}
|
|
||||||
|
|
||||||
const searchWords = searchText.split(" ").map(word => ({ keywords: word }));
|
|
||||||
|
|
||||||
const fuse = new Fuse(tools, { keys: ["keywords"], threshold: 0.5 });
|
|
||||||
const result = fuse.search({ $and: searchWords });
|
|
||||||
const filteredTools = result.map(({ item }) => item);
|
|
||||||
|
|
||||||
return { filteredTools };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const Container = () => {
|
|
||||||
const { filteredTools } = useRecoilValue(filteredToolsState);
|
|
||||||
|
|
||||||
return <Component {...{ filteredTools }} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Container;
|
export default Container;
|
||||||
|
|||||||
45
src/components/pages/search/Content.tsx
Normal file
45
src/components/pages/search/Content.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import equal from "fast-deep-equal";
|
||||||
|
import Fuse from "fuse.js";
|
||||||
|
import { ComponentPropsWithoutRef, memo } from "react";
|
||||||
|
import { selector, useRecoilValue } from "recoil";
|
||||||
|
|
||||||
|
import { Main, ToolCardGrid } from "@/components/common";
|
||||||
|
import { searchTextState } from "@/components/layout/states";
|
||||||
|
import { allTools } from "@/data/tools";
|
||||||
|
|
||||||
|
type Props = ComponentPropsWithoutRef<typeof ToolCardGrid>;
|
||||||
|
|
||||||
|
const StyledComponent = ({ tools }: Props) => (
|
||||||
|
<Main title="Searched tools">
|
||||||
|
<ToolCardGrid {...{ tools }} />
|
||||||
|
</Main>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Component = memo(StyledComponent, equal);
|
||||||
|
|
||||||
|
const filteredToolsState = selector({
|
||||||
|
key: "filteredToolsState",
|
||||||
|
get: ({ get }) => {
|
||||||
|
const searchText = get(searchTextState).trim();
|
||||||
|
|
||||||
|
if (searchText === "") {
|
||||||
|
return { filteredTools: allTools };
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchWords = searchText.split(" ").map(word => ({ keywords: word }));
|
||||||
|
|
||||||
|
const fuse = new Fuse(allTools, { keys: ["keywords"], threshold: 0.5 });
|
||||||
|
const result = fuse.search({ $and: searchWords });
|
||||||
|
const filteredTools = result.map(({ item }) => item);
|
||||||
|
|
||||||
|
return { filteredTools };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const Container = () => {
|
||||||
|
const { filteredTools } = useRecoilValue(filteredToolsState);
|
||||||
|
|
||||||
|
return <Component tools={filteredTools} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Container;
|
||||||
3
src/components/pages/search/index.ts
Normal file
3
src/components/pages/search/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import Content from "./Content";
|
||||||
|
|
||||||
|
export { Content };
|
||||||
107
src/data/tools.tsx
Normal file
107
src/data/tools.tsx
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import {
|
||||||
|
Code,
|
||||||
|
DataObject,
|
||||||
|
DragHandle,
|
||||||
|
Filter,
|
||||||
|
Fingerprint,
|
||||||
|
Key,
|
||||||
|
Link,
|
||||||
|
Numbers,
|
||||||
|
TextIncrease,
|
||||||
|
Transform,
|
||||||
|
} from "@mui/icons-material";
|
||||||
|
|
||||||
|
import { pagesPath } from "@/libs/$path";
|
||||||
|
|
||||||
|
export const allTools = [
|
||||||
|
{
|
||||||
|
icon: <Transform />,
|
||||||
|
title: "Json <> Yaml Converter",
|
||||||
|
description: "Convert Json data to Yaml and vice versa",
|
||||||
|
keywords: "json yaml converter",
|
||||||
|
href: pagesPath.converters.json_yaml.$url(),
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <Numbers />,
|
||||||
|
title: "Number Base Converter",
|
||||||
|
description: "Convert numbers from one base to another",
|
||||||
|
keywords: "number base converter",
|
||||||
|
href: pagesPath.converters.number_base.$url(),
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <Code />,
|
||||||
|
title: "HTML Encoder / Decoder",
|
||||||
|
description:
|
||||||
|
"Encode or decode all the applicable characters to their corresponding HTML entities",
|
||||||
|
keywords: "html encoder escaper decocder unescaper",
|
||||||
|
href: pagesPath.encoders_decoders.html.$url(),
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <Link />,
|
||||||
|
title: "URL Encoder / Decoder",
|
||||||
|
description:
|
||||||
|
"Encode or decode all the applicable characters to their corresponding URL entities",
|
||||||
|
keywords: "url encoder escaper decocder unescaper",
|
||||||
|
href: pagesPath.$url(),
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <DragHandle />,
|
||||||
|
title: "Base 64 Encoder / Decoder",
|
||||||
|
description: "Encode and decode Base64 data",
|
||||||
|
keywords: "base64 encoder decocder",
|
||||||
|
href: pagesPath.$url(),
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <Key />,
|
||||||
|
title: "JWT Decoder",
|
||||||
|
description: "Decode a JWT header, payload and signature",
|
||||||
|
keywords: "jwt json web token decocder",
|
||||||
|
href: pagesPath.$url(),
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <DataObject />,
|
||||||
|
title: "JSON Formatter",
|
||||||
|
description: "Indent or minify JSON data",
|
||||||
|
keywords: "json formatter",
|
||||||
|
href: pagesPath.$url(),
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <Fingerprint />,
|
||||||
|
title: "Hash Generator",
|
||||||
|
description: "Calculate MD5, SHA1, SHA256 and SHA512 hash from text data",
|
||||||
|
keywords: "hash generator md5 sha1 sha256 sha512",
|
||||||
|
href: pagesPath.$url(),
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <Numbers />,
|
||||||
|
title: "UUID Generator",
|
||||||
|
description: "Generate UUIDs version 1 and 4",
|
||||||
|
keywords: "guid uuid1 uuid4 generator",
|
||||||
|
href: pagesPath.$url(),
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <TextIncrease />,
|
||||||
|
title: "Regex Tester",
|
||||||
|
description: "Validate and test regular expressions",
|
||||||
|
keywords: "regular expression regex validator tester",
|
||||||
|
href: pagesPath.$url(),
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <Filter />,
|
||||||
|
title: "PNG / JPEG Compressor",
|
||||||
|
description: "Lossless PNG and JPEG optimizer",
|
||||||
|
keywords: "png jpeg compressor optimizer image",
|
||||||
|
href: pagesPath.$url(),
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -21,6 +21,9 @@ export const pagesPath = {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
search: {
|
||||||
|
$url: (url?: { hash?: string }) => ({ pathname: "/search" as const, hash: url?.hash }),
|
||||||
|
},
|
||||||
$url: (url?: { hash?: string }) => ({ pathname: "/" as const, hash: url?.hash }),
|
$url: (url?: { hash?: string }) => ({ pathname: "/" as const, hash: url?.hash }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
7
src/pages/search.tsx
Normal file
7
src/pages/search.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { NextPage } from "next";
|
||||||
|
|
||||||
|
import { Content } from "@/components/pages/search";
|
||||||
|
|
||||||
|
const Page: NextPage = Content;
|
||||||
|
|
||||||
|
export default Page;
|
||||||
Reference in New Issue
Block a user