feat: add searched tools page

This commit is contained in:
rusconn
2022-03-29 23:18:42 +09:00
parent d550594b08
commit 223327a5f2
10 changed files with 201 additions and 151 deletions

View 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;

View File

@@ -3,5 +3,6 @@ import Configurations from "./Configurations";
import Main from "./Main";
import MainItem from "./MainItem";
import TextField from "./TextField";
import ToolCardGrid from "./ToolCardGrid";
export { CodeEditorHalfLoading, Configurations, Main, MainItem, TextField };
export { CodeEditorHalfLoading, Configurations, Main, MainItem, TextField, ToolCardGrid };

View File

@@ -56,8 +56,11 @@ const Container = () => {
const onChange: Props["onChange"] = useCallback(
({ currentTarget: { value } }) => {
setText(value);
const url = value === "" ? pagesPath.$url() : pagesPath.search.$url();
// eslint-disable-next-line @typescript-eslint/no-floating-promises
router.push(pagesPath.$url());
router.push(url);
},
[setText, router]
);

View File

@@ -1,160 +1,18 @@
import {
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 { ComponentPropsWithoutRef, memo } from "react";
import { Main } from "@/components/common";
import { searchTextState } from "@/components/layout/states";
import { pagesPath } from "@/libs/$path";
import { Main, ToolCardGrid } from "@/components/common";
import { allTools } from "@/data/tools";
import Card from "./Card";
type Props = ComponentPropsWithoutRef<typeof ToolCardGrid>;
type 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) => (
const StyledComponent = ({ tools }: Props) => (
<Main title="All tools">
<Grid container rowSpacing={4} columnSpacing={2}>
{filteredTools.map(({ icon, title, description, href, disabled }) => (
<Grid key={title} item>
<Card {...{ icon, title, description, href, disabled }} />
</Grid>
))}
</Grid>
<ToolCardGrid {...{ tools }} />
</Main>
);
export const Component = memo(StyledComponent);
const filteredToolsState = selector({
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 }} />;
};
const Container = () => <Component tools={allTools} />;
export default Container;

View 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;

View File

@@ -0,0 +1,3 @@
import Content from "./Content";
export { Content };

107
src/data/tools.tsx Normal file
View 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,
},
];

View File

@@ -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 }),
};

7
src/pages/search.tsx Normal file
View File

@@ -0,0 +1,7 @@
import type { NextPage } from "next";
import { Content } from "@/components/pages/search";
const Page: NextPage = Content;
export default Page;