mirror of
https://github.com/ershisan99/DevToysWeb.git
synced 2026-01-27 20:52:07 +00:00
feat: add Json <> Yaml converter
This commit is contained in:
50
src/components/pages/converters/json-yaml/Configuration.tsx
Normal file
50
src/components/pages/converters/json-yaml/Configuration.tsx
Normal 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] 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} spaces
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
export const Component = memo(StyledComponent);
|
||||
|
||||
export default Component;
|
||||
128
src/components/pages/converters/json-yaml/Content.tsx
Normal file
128
src/components/pages/converters/json-yaml/Content.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import { Stack } from "@mui/material";
|
||||
import dynamic from "next/dynamic";
|
||||
import { ComponentPropsWithoutRef, memo, useCallback, useState } from "react";
|
||||
import YAML from "yaml";
|
||||
|
||||
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 Promise.all([
|
||||
import("ace-builds/src-noconflict/mode-json"),
|
||||
import("ace-builds/src-noconflict/mode-yaml"),
|
||||
]);
|
||||
return ace;
|
||||
},
|
||||
{ ssr: false, loading: () => <CodeEditorHalfLoading /> }
|
||||
);
|
||||
|
||||
type CodeValue = NonNullable<ComponentPropsWithoutRef<typeof CodeEditorHalf>["value"]>;
|
||||
type OnCodeChange = NonNullable<ComponentPropsWithoutRef<typeof CodeEditorHalf>["onChange"]>;
|
||||
|
||||
type Props = {
|
||||
json: CodeValue;
|
||||
yaml: CodeValue;
|
||||
onJsonChange: OnCodeChange;
|
||||
onYamlChange: OnCodeChange;
|
||||
} & ComponentPropsWithoutRef<typeof Configuration>;
|
||||
|
||||
const StyledComponent = ({
|
||||
json,
|
||||
yaml,
|
||||
spaces,
|
||||
onJsonChange,
|
||||
onYamlChange,
|
||||
onSpacesChange,
|
||||
}: Props) => (
|
||||
<Main title="Json <> Yaml Converter">
|
||||
<MainItem title="Configuration">
|
||||
<Configuration {...{ spaces, onSpacesChange }} />
|
||||
</MainItem>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<MainItem title="Json">
|
||||
<CodeEditorHalf
|
||||
name="json"
|
||||
mode="json"
|
||||
value={json}
|
||||
tabSize={spaces}
|
||||
onChange={onJsonChange}
|
||||
/>
|
||||
</MainItem>
|
||||
<MainItem title="Yaml">
|
||||
<CodeEditorHalf
|
||||
name="yaml"
|
||||
mode="yaml"
|
||||
value={yaml}
|
||||
tabSize={spaces}
|
||||
onChange={onYamlChange}
|
||||
/>
|
||||
</MainItem>
|
||||
</Stack>
|
||||
</Main>
|
||||
);
|
||||
|
||||
export const Component = memo(StyledComponent);
|
||||
|
||||
const Container = () => {
|
||||
const [json, setJson] = useState('{\n "foo": "bar"\n}');
|
||||
const [yaml, setYaml] = useState("foo: bar");
|
||||
const [spaces, setSpaces] = useState<Spaces>(2);
|
||||
|
||||
const onJsonChange: Props["onJsonChange"] = useCallback(
|
||||
value => {
|
||||
setJson(value);
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(value) as unknown;
|
||||
setYaml(YAML.stringify(parsed, { indent: spaces, simpleKeys: true }));
|
||||
} catch {
|
||||
setYaml("");
|
||||
}
|
||||
},
|
||||
[spaces]
|
||||
);
|
||||
|
||||
const onYamlChange: Props["onYamlChange"] = useCallback(
|
||||
value => {
|
||||
setYaml(value);
|
||||
|
||||
try {
|
||||
const parsed = YAML.parse(value, { merge: true }) as unknown;
|
||||
setJson(JSON.stringify(parsed, null, spaces));
|
||||
} catch {
|
||||
setJson("");
|
||||
}
|
||||
},
|
||||
[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;
|
||||
setJson(JSON.stringify(parsed, null, newSpaces));
|
||||
setYaml(YAML.stringify(parsed, { indent: newSpaces, simpleKeys: true }));
|
||||
} catch {
|
||||
setJson("");
|
||||
setYaml("");
|
||||
}
|
||||
},
|
||||
[json]
|
||||
);
|
||||
|
||||
return <Component {...{ json, yaml, spaces, onJsonChange, onYamlChange, onSpacesChange }} />;
|
||||
};
|
||||
|
||||
export default Container;
|
||||
3
src/components/pages/converters/json-yaml/index.ts
Normal file
3
src/components/pages/converters/json-yaml/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import Content from "./Content";
|
||||
|
||||
export { Content };
|
||||
Reference in New Issue
Block a user