Skip to content

Commit e16825a

Browse files
committed
feat: add json yaml converter
1 parent c2cd3db commit e16825a

File tree

7 files changed

+217
-1
lines changed

7 files changed

+217
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import {
2+
Flex,
3+
Box,
4+
IconButton,
5+
VStack,
6+
HStack,
7+
Text,
8+
Heading,
9+
RadioGroup,
10+
Radio,
11+
FormControl,
12+
FormLabel,
13+
useClipboard,
14+
} from "@chakra-ui/react";
15+
import { CopyIcon, CloseIcon } from "@chakra-ui/icons";
16+
import { Editor, EditorChange } from "codemirror";
17+
import { Controlled } from "react-codemirror2";
18+
import { useState, useEffect } from "react";
19+
import "codemirror/lib/codemirror.css";
20+
import "codemirror/theme/material.css";
21+
22+
type ConvertType = "toYAML" | "toJSON";
23+
const defaultIndent = 2;
24+
25+
const JsonYaml = () => {
26+
const [input, setInput] = useState<string>("");
27+
const [output, setOutput] = useState<string>("");
28+
const [indent, setIndent] = useState<number>(defaultIndent);
29+
const [convertType, setConvertType] = useState<ConvertType>("toYAML");
30+
const { onCopy } = useClipboard(output);
31+
32+
const handleChange = (editor: Editor, data: EditorChange, value: string) => {
33+
setInput(value);
34+
};
35+
36+
useEffect(() => {
37+
const f = async () => {
38+
const { toJson, toYaml } = await import("@/utils/json-yaml");
39+
if (input === "") {
40+
setOutput("");
41+
return;
42+
}
43+
try {
44+
if (convertType === "toYAML") {
45+
setOutput(toYaml(input, indent));
46+
} else {
47+
setOutput(toJson(input, indent));
48+
}
49+
} catch (e: any) {
50+
setOutput(e.message);
51+
}
52+
};
53+
f();
54+
}, [input, indent, convertType]);
55+
56+
return (
57+
<VStack spacing="5">
58+
<HStack spacing="5" w="100%">
59+
{/* Input Box */}
60+
<Box w="45%">
61+
<Flex justifyContent={"space-between"}>
62+
<Text fontSize="2xl" mb="10px" ml="45%">
63+
Input
64+
</Text>
65+
<HStack spacing="2">
66+
<IconButton
67+
aria-label="Clear"
68+
icon={<CloseIcon />}
69+
onClick={e => setInput("")}
70+
/>
71+
</HStack>
72+
</Flex>
73+
<Controlled
74+
onBeforeChange={handleChange}
75+
value={input}
76+
options={{
77+
lineNumbers: true,
78+
mode: convertType === "toYAML" ? "json" : "yaml",
79+
theme: "material",
80+
}}
81+
/>
82+
</Box>
83+
{/* Output Box */}
84+
<Box w="45%">
85+
<Flex justifyContent={"space-between"}>
86+
<Text fontSize="2xl" mb="10px" ml="45%">
87+
Output
88+
</Text>
89+
<HStack spacing="2">
90+
<IconButton
91+
aria-label="Copy"
92+
icon={<CopyIcon />}
93+
onClick={onCopy}
94+
/>
95+
</HStack>
96+
</Flex>
97+
<Controlled
98+
onBeforeChange={handleChange}
99+
value={output}
100+
options={{
101+
lineNumbers: true,
102+
mode: convertType === "toYAML" ? "yaml" : "json",
103+
readOnly: true,
104+
theme: "material",
105+
}}
106+
/>
107+
</Box>
108+
</HStack>
109+
110+
{/* Configuration part */}
111+
<Flex w="100%">
112+
<VStack spacing="3">
113+
<Heading size="lg">Configuration</Heading>
114+
115+
<FormControl as="fieldset">
116+
<FormLabel as="legend">Convert target</FormLabel>
117+
<RadioGroup
118+
value={convertType}
119+
onChange={value => setConvertType(value as ConvertType)}
120+
>
121+
<HStack>
122+
<Radio value="toYAML">To YAML</Radio>
123+
<Radio value="toJSON">To JSON</Radio>
124+
</HStack>
125+
</RadioGroup>
126+
</FormControl>
127+
128+
<FormControl as="fieldset">
129+
<FormLabel as="legend">Indent</FormLabel>
130+
<RadioGroup
131+
onChange={value => setIndent(Number(value))}
132+
value={indent.toString()}
133+
>
134+
<HStack direction="row">
135+
<Radio value="2">2</Radio>
136+
<Radio value="4">4</Radio>
137+
</HStack>
138+
</RadioGroup>
139+
</FormControl>
140+
</VStack>
141+
</Flex>
142+
</VStack>
143+
);
144+
};
145+
146+
export default JsonYaml;

data/categories.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import RegexTester from "@/data/tools/testers/regexTester";
88
import ColorContrastChecker from "@/data/tools/color/colorContrastChecker";
99
import Base64EncoderDecoder from "@/data/tools/converters/base64EncoderDecoder";
1010
import HtmlToJsx from "@/data/tools/converters/HtmlToJsx";
11+
import JsonYaml from "@/data/tools/converters/JsonYaml";
1112

1213
const categories: Category[] = [
1314
{
@@ -41,7 +42,7 @@ const categories: Category[] = [
4142
{
4243
id: 5,
4344
name: "Converters",
44-
children: [Base64EncoderDecoder, HtmlToJsx],
45+
children: [Base64EncoderDecoder, HtmlToJsx, JsonYaml],
4546
slug: "converters",
4647
directory: "Converters",
4748
},

data/tools/converters/JsonYaml.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Tool } from "@/data/types";
2+
3+
const JsonYaml: Tool = {
4+
id: 3,
5+
name: "JSON <> YAML",
6+
description: "Converts between JSON and YAML",
7+
componentFileName: "JsonYaml",
8+
slug: "json-yaml",
9+
categorySlug: "converters",
10+
acceptedDataTypes: ["json", "yaml"],
11+
};
12+
13+
export default JsonYaml;

data/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,6 @@ export type DataTypes =
2525
| "mp4"
2626
| "regex"
2727
| "html"
28+
| "json"
29+
| "yaml"
2830
| "jsx";

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,22 @@
2020
"snyk-protect": "snyk-protect"
2121
},
2222
"dependencies": {
23+
"@chakra-ui/icons": "^1.1.1",
2324
"@chakra-ui/react": "^1.7.3",
2425
"@emotion/react": "^11.7.1",
2526
"@emotion/styled": "^11.6.0",
2627
"@ffmpeg/core": "^0.10.0",
2728
"@ffmpeg/ffmpeg": "^0.9.8",
2829
"@snyk/protect": "1.812.0",
2930
"@types/file-saver": "^2.0.4",
31+
"@types/js-yaml": "^4.0.5",
3032
"chroma-js": "^2.1.2",
3133
"codemirror": "^5.65.0",
3234
"file-saver": "^2.0.5",
3335
"formik": "^2.2.9",
3436
"framer-motion": "^4",
3537
"fuse.js": "^6.5.3",
38+
"js-yaml": "^4.1.0",
3639
"next": "^12.0.7",
3740
"next-pwa": "^5.4.1",
3841
"next-seo": "^4.28.1",

utils/json-yaml.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import yaml from "js-yaml";
2+
3+
export const toJson = (src: string, indent: number): string => {
4+
return JSON.stringify(yaml.load(src), null, indent);
5+
};
6+
7+
export const toYaml = (src: string, indent: number): string => {
8+
const opt = { indent: indent } as yaml.DumpOptions;
9+
return yaml.dump(JSON.parse(src), opt);
10+
};

yarn.lock

+41
Original file line numberDiff line numberDiff line change
@@ -1068,13 +1068,28 @@
10681068
compute-scroll-into-view "1.0.14"
10691069
copy-to-clipboard "3.3.1"
10701070

1071+
"@chakra-ui/icon@1.2.1":
1072+
version "1.2.1"
1073+
resolved "https://registry.yarnpkg.com/@chakra-ui/icon/-/icon-1.2.1.tgz#b47c0016531da8bef81061d2b96a2d5282b07ff8"
1074+
integrity sha512-uZxFsiY4Tld+LvGIX7cky0H6oMRac8udPMQRzIk/UQeNZcsWisGetatbQsew3y1lWV/iH/8+TlDuW13GWGyGGQ==
1075+
dependencies:
1076+
"@chakra-ui/utils" "1.9.1"
1077+
10711078
"@chakra-ui/icon@2.0.0":
10721079
version "2.0.0"
10731080
resolved "https://registry.yarnpkg.com/@chakra-ui/icon/-/icon-2.0.0.tgz#a2468736117139f94c6ed65eb86b9838e554c90e"
10741081
integrity sha512-/GuU+xIcOIy9uSUUUCu249ZJB/nLDbjWGkfpoSdBwqT4+ytJrKt+0Ckh3Ub14sz3BJD+Z6IiIt6ySOA9+7lbsA==
10751082
dependencies:
10761083
"@chakra-ui/utils" "1.9.1"
10771084

1085+
"@chakra-ui/icons@^1.1.1":
1086+
version "1.1.1"
1087+
resolved "https://registry.yarnpkg.com/@chakra-ui/icons/-/icons-1.1.1.tgz#e4b191fd38be999c4434ff2b1fb69a5eaf3cf226"
1088+
integrity sha512-/+u6euCOFw6J1DZW7NcVFtib4mygJBoqRdsKiU1Z3uiTC+zQTBzcCt54NQ+kK8rhuNzJ+odahnt/AbjBJgZ+5A==
1089+
dependencies:
1090+
"@chakra-ui/icon" "1.2.1"
1091+
"@types/react" "^17.0.15"
1092+
10781093
"@chakra-ui/image@1.1.1":
10791094
version "1.1.1"
10801095
resolved "https://registry.npmjs.org/@chakra-ui/image/-/image-1.1.1.tgz"
@@ -2044,6 +2059,11 @@
20442059
"@types/minimatch" "*"
20452060
"@types/node" "*"
20462061

2062+
"@types/js-yaml@^4.0.5":
2063+
version "4.0.5"
2064+
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138"
2065+
integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==
2066+
20472067
"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8":
20482068
version "7.0.9"
20492069
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz"
@@ -2110,6 +2130,15 @@
21102130
"@types/scheduler" "*"
21112131
csstype "^3.0.2"
21122132

2133+
"@types/react@^17.0.15":
2134+
version "17.0.38"
2135+
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.38.tgz#f24249fefd89357d5fa71f739a686b8d7c7202bd"
2136+
integrity sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==
2137+
dependencies:
2138+
"@types/prop-types" "*"
2139+
"@types/scheduler" "*"
2140+
csstype "^3.0.2"
2141+
21132142
"@types/resolve@1.17.1":
21142143
version "1.17.1"
21152144
resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz"
@@ -2295,6 +2324,11 @@ argparse@^1.0.7:
22952324
dependencies:
22962325
sprintf-js "~1.0.2"
22972326

2327+
argparse@^2.0.1:
2328+
version "2.0.1"
2329+
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
2330+
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
2331+
22982332
aria-hidden@^1.1.1:
22992333
version "1.1.3"
23002334
resolved "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.1.3.tgz"
@@ -4692,6 +4726,13 @@ js-yaml@^3.13.1:
46924726
argparse "^1.0.7"
46934727
esprima "^4.0.0"
46944728

4729+
js-yaml@^4.1.0:
4730+
version "4.1.0"
4731+
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
4732+
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
4733+
dependencies:
4734+
argparse "^2.0.1"
4735+
46954736
jsesc@^2.5.1:
46964737
version "2.5.2"
46974738
resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz"

0 commit comments

Comments
 (0)