feat: add json formatter

This commit is contained in:
rusconn
2022-04-01 21:49:16 +09:00
parent a4267f4caf
commit 3d2c74804e
7 changed files with 159 additions and 3 deletions

View File

@@ -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

View File

@@ -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<T> = NonNullable<SelectInputProps<T>["onChange"]>;
type Props = {
spaces: Spaces;
onSpacesChange: OnSelectChange<Spaces>;
};
const select = css`
& .MuiSelect-select:focus {
background-color: transparent;
}
`;
const StyledComponent = ({ spaces, onSpacesChange }: Props) => (
<Configurations
configurations={[
{
icon: <SpaceBar />,
title: "Indentation",
input: (
<FormControl variant="standard">
<Select value={spaces} onChange={onSpacesChange} css={select}>
{spacesArray.map(value => (
<MenuItem key={value} {...{ value }}>
{value === 0 ? "minified" : `${value} spaces`}
</MenuItem>
))}
</Select>
</FormControl>
),
},
]}
/>
);
export const Component = memo(StyledComponent);
export default Component;

View File

@@ -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: () => <CodeEditorHalfLoading /> }
);
type CodeValue = NonNullable<ComponentPropsWithoutRef<typeof CodeEditorHalf>["value"]>;
type OnCodeChange = NonNullable<ComponentPropsWithoutRef<typeof CodeEditorHalf>["onChange"]>;
type Props = {
json: CodeValue;
formatted: CodeValue;
onJsonChange: OnCodeChange;
} & ComponentPropsWithoutRef<typeof Configuration>;
const StyledComponent = ({ json, formatted, spaces, onJsonChange, onSpacesChange }: Props) => (
<Main title="Json Formatter">
<MainItem title="Configuration">
<Configuration {...{ spaces, onSpacesChange }} />
</MainItem>
<Stack direction="row" spacing={2}>
<MainItem title="Input">
<CodeEditorHalf name="json" mode="json" value={json} tabSize={2} onChange={onJsonChange} />
</MainItem>
<MainItem title="Output">
<CodeEditorHalf name="formatted" mode="json" value={formatted} tabSize={spaces} readOnly />
</MainItem>
</Stack>
</Main>
);
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<Spaces>(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 <Component {...{ json, formatted, spaces, onJsonChange, onSpacesChange }} />;
};
export default Container;

View File

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

View File

@@ -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,
},
],
},

View File

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

View File

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