From 3d2c74804e214e7c21894a72a8c9de98e2cc7751 Mon Sep 17 00:00:00 2001 From: rusconn Date: Fri, 1 Apr 2022 21:49:16 +0900 Subject: [PATCH] feat: add json formatter --- README.md | 2 +- .../pages/formatters/json/Configuration.tsx | 50 +++++++++++ .../pages/formatters/json/Content.tsx | 88 +++++++++++++++++++ src/components/pages/formatters/json/index.ts | 3 + src/data/tools.tsx | 4 +- src/libs/$path.ts | 8 ++ src/pages/formatters/json.tsx | 7 ++ 7 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 src/components/pages/formatters/json/Configuration.tsx create mode 100644 src/components/pages/formatters/json/Content.tsx create mode 100644 src/components/pages/formatters/json/index.ts create mode 100644 src/pages/formatters/json.tsx diff --git a/README.md b/README.md index 7365912..ed64c95 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A web clone of [DevToys](https://github.com/veler/DevToys) - [ ] Implement tools - [x] Converters - [x] Encoders / Decoders - - [ ] Formatters + - [x] Formatters - [ ] Generators - [ ] Text - [ ] Graphic diff --git a/src/components/pages/formatters/json/Configuration.tsx b/src/components/pages/formatters/json/Configuration.tsx new file mode 100644 index 0000000..40563e7 --- /dev/null +++ b/src/components/pages/formatters/json/Configuration.tsx @@ -0,0 +1,50 @@ +import { SpaceBar } from "@mui/icons-material"; +import { FormControl, MenuItem, Select } from "@mui/material"; +import { SelectInputProps } from "@mui/material/Select/SelectInput"; +import { css } from "@mui/material/styles"; +import { memo } from "react"; + +import { Configurations } from "@/components/common"; + +const spacesArray = [2, 4, 0] as const; +export type Spaces = typeof spacesArray[number]; +export const isSpaces = (x: number): x is Spaces => spacesArray.includes(x as Spaces); + +type OnSelectChange = NonNullable["onChange"]>; + +type Props = { + spaces: Spaces; + onSpacesChange: OnSelectChange; +}; + +const select = css` + & .MuiSelect-select:focus { + background-color: transparent; + } +`; + +const StyledComponent = ({ spaces, onSpacesChange }: Props) => ( + , + title: "Indentation", + input: ( + + + + ), + }, + ]} + /> +); + +export const Component = memo(StyledComponent); + +export default Component; diff --git a/src/components/pages/formatters/json/Content.tsx b/src/components/pages/formatters/json/Content.tsx new file mode 100644 index 0000000..146f876 --- /dev/null +++ b/src/components/pages/formatters/json/Content.tsx @@ -0,0 +1,88 @@ +import { Stack } from "@mui/material"; +import dynamic from "next/dynamic"; +import { ComponentPropsWithoutRef, memo, useCallback, useState } from "react"; + +import { CodeEditorHalfLoading, Main, MainItem } from "@/components/common"; + +import Configuration, { isSpaces, Spaces } from "./Configuration"; + +// https://github.com/securingsincity/react-ace/issues/27 +const CodeEditorHalf = dynamic( + async () => { + const ace = await import("@/components/common/CodeEditorHalf"); + await import("ace-builds/src-noconflict/mode-json"); + return ace; + }, + { ssr: false, loading: () => } +); + +type CodeValue = NonNullable["value"]>; +type OnCodeChange = NonNullable["onChange"]>; + +type Props = { + json: CodeValue; + formatted: CodeValue; + onJsonChange: OnCodeChange; +} & ComponentPropsWithoutRef; + +const StyledComponent = ({ json, formatted, spaces, onJsonChange, onSpacesChange }: Props) => ( +
+ + + + + + + + + + + +
+); + +export const Component = memo(StyledComponent); + +const Container = () => { + const [json, setJson] = useState('{"foo":"bar"}'); + const [formatted, setFormatted] = useState('{\n "foo": "bar"\n}'); + const [spaces, setSpaces] = useState(2); + + const onJsonChange: Props["onJsonChange"] = useCallback( + value => { + setJson(value); + + try { + const parsed = JSON.parse(value) as unknown; + setFormatted(JSON.stringify(parsed, null, spaces)); + } catch { + setFormatted(""); + } + }, + [spaces] + ); + + const onSpacesChange: Props["onSpacesChange"] = useCallback( + ({ target: { value } }) => { + const newSpaces = Number(value); + + if (!isSpaces(newSpaces)) { + return; + } + + setSpaces(newSpaces); + + try { + const parsed = JSON.parse(json) as unknown; + setFormatted(JSON.stringify(parsed, null, newSpaces)); + } catch { + setFormatted(""); + } + }, + [json] + ); + + return ; +}; + +export default Container; diff --git a/src/components/pages/formatters/json/index.ts b/src/components/pages/formatters/json/index.ts new file mode 100644 index 0000000..ae1b8af --- /dev/null +++ b/src/components/pages/formatters/json/index.ts @@ -0,0 +1,3 @@ +import Content from "./Content"; + +export { Content }; diff --git a/src/data/tools.tsx b/src/data/tools.tsx index 67f0345..ede2840 100644 --- a/src/data/tools.tsx +++ b/src/data/tools.tsx @@ -97,8 +97,8 @@ const toolGroups = [ longTitle: "JSON Formatter", description: "Indent or minify JSON data", keywords: "json formatter", - href: pagesPath.$url(), - disabled: true, + href: pagesPath.formatters.json.$url(), + disabled: false, }, ], }, diff --git a/src/libs/$path.ts b/src/libs/$path.ts index 911d6c6..134c018 100644 --- a/src/libs/$path.ts +++ b/src/libs/$path.ts @@ -39,6 +39,14 @@ export const pagesPath = { }), }, }, + formatters: { + json: { + $url: (url?: { hash?: string }) => ({ + pathname: "/formatters/json" as const, + hash: url?.hash, + }), + }, + }, search: { $url: (url?: { hash?: string }) => ({ pathname: "/search" as const, hash: url?.hash }), }, diff --git a/src/pages/formatters/json.tsx b/src/pages/formatters/json.tsx new file mode 100644 index 0000000..6a3aa3a --- /dev/null +++ b/src/pages/formatters/json.tsx @@ -0,0 +1,7 @@ +import type { NextPage } from "next"; + +import { Content } from "@/components/pages/formatters/json"; + +const Page: NextPage = Content; + +export default Page;