diff --git a/src/containers/ConverterLayout/PageLinks.tsx b/src/containers/ConverterLayout/PageLinks.tsx new file mode 100644 index 0000000..1b64180 --- /dev/null +++ b/src/containers/ConverterLayout/PageLinks.tsx @@ -0,0 +1,69 @@ +import React from "react"; +import Link from "next/link"; +import { Anchor, Button, Flex, List, SimpleGrid, Stack } from "@mantine/core"; +import { FaArrowRightLong } from "react-icons/fa6"; +import { formats } from "src/enums/file.enum"; + +const languages = formats.map(format => format.label); + +function groupCombinations(array: string[]): Record { + // Create an object to hold the grouped combinations + const grouped = {}; + + // Iterate over each item in the array + array.forEach(from => { + // Filter out the same item for the "to" array + const targets = array.filter(to => to !== from); + + // Add the "from" item as the key and the "to" items as the value array + grouped[from] = targets; + }); + + return grouped; +} + +const groupedLanguages = groupCombinations(languages); + +export const PageLinks = () => { + return ( + + + + + + {Object.entries(groupedLanguages).map(([from, tos]) => ( + + {tos.map(to => ( + + + {from} to {to} + + + ))} + + ))} + + + ); +}; diff --git a/src/containers/ConverterLayout/ToolPage.tsx b/src/containers/ConverterLayout/ToolPage.tsx new file mode 100644 index 0000000..56678ce --- /dev/null +++ b/src/containers/ConverterLayout/ToolPage.tsx @@ -0,0 +1,112 @@ +import React, { useEffect, useRef } from "react"; +import { Box, Container, Flex, Paper, Text, Title } from "@mantine/core"; +import { Editor } from "@monaco-editor/react"; +import { NextSeo } from "next-seo"; +import { LuCheck, LuXCircle } from "react-icons/lu"; +import { SEO } from "src/constants/seo"; +import { PageLinks } from "src/containers/ConverterLayout/PageLinks"; +import { editorOptions } from "src/containers/ConverterLayout/options"; +import { type FileFormat, formats } from "src/enums/file.enum"; +import Layout from "src/layout/Layout"; +import { contentToJson, jsonToContent } from "src/lib/utils/jsonAdapter"; + +interface ToolPageProps { + from: FileFormat; + to: FileFormat; +} + +export const ToolPage = ({ from, to }: ToolPageProps) => { + const editorRef = useRef(null); + const [contentHasError, setContentHasError] = React.useState(false); + const [originalContent, setOriginalContent] = React.useState(""); + const [convertedContent, setConvertedContent] = React.useState(""); + const [scrollPosition, setScrollPosition] = React.useState(0); + const [editorHeight, setEditorHeight] = React.useState(0); + + useEffect(() => { + if (!originalContent.length) return; + + (async () => { + try { + const json = await contentToJson(originalContent, from); + const content = await jsonToContent(JSON.stringify(json), to); + setConvertedContent(content); + setContentHasError(false); + } catch (_e) { + setContentHasError(true); + setConvertedContent(""); + } + })(); + }, [from, originalContent, to]); + + useEffect(() => { + const scrollPositionRatio = + (scrollPosition / editorHeight) * (editorRef.current?.getContentHeight() || 0); + + editorRef.current?.setScrollTop(scrollPositionRatio); + }, [editorHeight, scrollPosition]); + + return ( + + value === from)?.label} to ${formats.find(({ value }) => value === to)?.label} | ToDiagram`} + canonical={`https://todiagram.com/converter/${from}-to-${to}`} + /> + + + {formats.find(({ value }) => value === from)?.label} to{" "} + {formats.find(({ value }) => value === to)?.label} Converter + + + + + + + {formats.find(({ value }) => value === from)?.label} + {contentHasError && !!originalContent ? ( + + ) : ( + + )} + + + setOriginalContent(value || "")} + language={from} + height={500} + options={editorOptions} + onMount={editor => { + editor.onDidContentSizeChange(() => { + setEditorHeight(editor.getContentHeight()); + }); + + editor.onDidScrollChange(e => { + setScrollPosition(e.scrollTop); + }); + }} + /> + + + + {formats.find(({ value }) => value === to)?.label} + + { + editorRef.current = editor; + }} + /> + + + + + ); +}; diff --git a/src/containers/ConverterLayout/options.ts b/src/containers/ConverterLayout/options.ts new file mode 100644 index 0000000..08c504c --- /dev/null +++ b/src/containers/ConverterLayout/options.ts @@ -0,0 +1,9 @@ +import type { EditorProps } from "@monaco-editor/react"; + +export const editorOptions: EditorProps["options"] = { + formatOnPaste: true, + stopRenderingLineAfter: -1, + minimap: { enabled: false }, + stickyScroll: { enabled: false }, + scrollBeyondLastLine: false, +}; diff --git a/src/containers/Toolbar/index.tsx b/src/containers/Toolbar/index.tsx index 22ae579..fe40fad 100644 --- a/src/containers/Toolbar/index.tsx +++ b/src/containers/Toolbar/index.tsx @@ -6,7 +6,7 @@ import { AiOutlineFullscreen } from "react-icons/ai"; import { FiDownload } from "react-icons/fi"; import { LuCrown } from "react-icons/lu"; import { SearchInput } from "src/containers/Toolbar/SearchInput"; -import { FileFormat } from "src/enums/file.enum"; +import { type FileFormat, formats } from "src/enums/file.enum"; import { JSONCrackLogo } from "src/layout/JsonCrackLogo"; import useFile from "src/store/useFile"; import useModal from "src/store/useModal"; @@ -75,13 +75,7 @@ export const Toolbar = ({ isWidget = false }: ToolbarProps) => { onChange={e => setFormat(e as FileFormat)} miw={80} w={120} - data={[ - { value: FileFormat.JSON, label: "JSON" }, - { value: FileFormat.YAML, label: "YAML" }, - { value: FileFormat.XML, label: "XML" }, - { value: FileFormat.TOML, label: "TOML" }, - { value: FileFormat.CSV, label: "CSV" }, - ]} + data={formats} allowDeselect={false} /> diff --git a/src/containers/TypeLayout/PageLinks.tsx b/src/containers/TypeLayout/PageLinks.tsx new file mode 100644 index 0000000..c71b87d --- /dev/null +++ b/src/containers/TypeLayout/PageLinks.tsx @@ -0,0 +1,78 @@ +import React from "react"; +import Link from "next/link"; +import { Anchor, Button, Flex, List, SimpleGrid, Stack } from "@mantine/core"; +import { FaArrowRightLong } from "react-icons/fa6"; +import { formats, TypeLanguage, typeOptions } from "src/enums/file.enum"; + +type MappedCombinations = { + [language: string]: string[]; // Maps each language to an array of programming languages +}; + +function mapLanguagesToProgramming( + languages: string[], + programmingLanguages: string[] +): MappedCombinations { + const mappedCombinations: MappedCombinations = {}; + + // Iterate over each language + languages.forEach(language => { + // Assign the array of programming languages to each language key + mappedCombinations[language] = programmingLanguages; + }); + + return mappedCombinations; +} + +const filterProgrammingLanguages = [TypeLanguage.TypeScript_Combined, TypeLanguage.JSON_SCHEMA]; + +const languages = formats.map(format => format.label); + +const programmingLanguages = typeOptions + .filter(option => !filterProgrammingLanguages.includes(option.value)) + .map(option => option.label); + +const groupedLanguages = mapLanguagesToProgramming(languages, programmingLanguages); + +export const PageLinks = () => { + return ( + + + + + + {Object.entries(groupedLanguages).map(([from, tos]) => ( + + {tos.map(to => ( + + + {from} to {to} + + + ))} + + ))} + + + ); +}; diff --git a/src/containers/TypeLayout/TypegenWrapper.tsx b/src/containers/TypeLayout/TypegenWrapper.tsx new file mode 100644 index 0000000..b2c7c60 --- /dev/null +++ b/src/containers/TypeLayout/TypegenWrapper.tsx @@ -0,0 +1,93 @@ +import React, { useEffect, useRef } from "react"; +import { Box, Container, Flex, Paper, Title, Text } from "@mantine/core"; +import { Editor } from "@monaco-editor/react"; +import { NextSeo } from "next-seo"; +import { LuCheck, LuXCircle } from "react-icons/lu"; +import { SEO } from "src/constants/seo"; +import { editorOptions } from "src/containers/ConverterLayout/options"; +import { type FileFormat, formats, type TypeLanguage, typeOptions } from "src/enums/file.enum"; +import Layout from "src/layout/Layout"; +import { generateType } from "src/lib/utils/generateType"; +import { PageLinks } from "./PageLinks"; + +interface ConverterPagesProps { + from: FileFormat; + to: TypeLanguage; +} + +export const TypegenWrapper = ({ from, to }: ConverterPagesProps) => { + const editorRef = useRef(null); + const [contentHasError, setContentHasError] = React.useState(false); + const [originalContent, setOriginalContent] = React.useState(""); + const [convertedContent, setConvertedContent] = React.useState(""); + + useEffect(() => { + if (!originalContent.length) return; + + (async () => { + try { + const type = await generateType(originalContent, from, to); + setConvertedContent(type); + setContentHasError(false); + } catch (_e) { + setContentHasError(true); + setConvertedContent(""); + } + })(); + }, [from, originalContent, to]); + + return ( + + value === from)?.label} to ${typeOptions.find(({ value }) => value === to)?.label} | ToDiagram`} + canonical={`https://todiagram.com/converter/${from}-to-${to}`} + /> + + + {formats.find(({ value }) => value === from)?.label} to{" "} + {typeOptions.find(({ value }) => value === to)?.label} Converter + + + + + + + {formats.find(({ value }) => value === from)?.label} + {contentHasError && !!originalContent ? ( + + ) : ( + + )} + + + setOriginalContent(value || "")} + language={from} + height={500} + options={editorOptions} + /> + + + + {typeOptions.find(({ value }) => value === to)?.label} + + { + editorRef.current = editor; + }} + /> + + + + + ); +}; diff --git a/src/enums/file.enum.ts b/src/enums/file.enum.ts index 702dd7a..06441ef 100644 --- a/src/enums/file.enum.ts +++ b/src/enums/file.enum.ts @@ -5,3 +5,52 @@ export enum FileFormat { "TOML" = "toml", "CSV" = "csv", } + +export const formats = [ + { value: FileFormat.JSON, label: "JSON" }, + { value: FileFormat.YAML, label: "YAML" }, + { value: FileFormat.XML, label: "XML" }, + { value: FileFormat.CSV, label: "CSV" }, +]; + +export enum TypeLanguage { + TypeScript = "typescript", + TypeScript_Combined = "typescript/typealias", + Go = "go", + JSON_SCHEMA = "json_schema", + Kotlin = "kotlin", + Rust = "rust", +} + +export const typeOptions = [ + { + label: "TypeScript", + value: TypeLanguage.TypeScript, + lang: "typescript", + }, + { + label: "TypeScript (merged)", + value: TypeLanguage.TypeScript_Combined, + lang: "typescript", + }, + { + label: "Go", + value: TypeLanguage.Go, + lang: "go", + }, + { + label: "JSON Schema", + value: TypeLanguage.JSON_SCHEMA, + lang: "json", + }, + { + label: "Kotlin", + value: TypeLanguage.Kotlin, + lang: "kotlin", + }, + { + label: "Rust", + value: TypeLanguage.Rust, + lang: "rust", + }, +]; diff --git a/src/layout/Navbar.tsx b/src/layout/Navbar.tsx index 1c27f01..f948fe8 100644 --- a/src/layout/Navbar.tsx +++ b/src/layout/Navbar.tsx @@ -1,7 +1,8 @@ import React from "react"; import Link from "next/link"; -import { Button } from "@mantine/core"; +import { Button, Menu, type MenuItemProps, Text, Stack } from "@mantine/core"; import styled from "styled-components"; +import { LuChevronDown } from "react-icons/lu"; import { JSONCrackLogo } from "./JsonCrackLogo"; const StyledNavbarWrapper = styled.div` @@ -9,6 +10,14 @@ const StyledNavbarWrapper = styled.div` transition: background 0.2s ease-in-out; `; +const StyledMenuItem = styled(Menu.Item)` + color: black; + + &[data-hovered] { + background-color: #f7f7f7; + } +`; + const StyledNavbar = styled.nav` display: flex; justify-content: space-between; @@ -82,17 +91,55 @@ export const Navbar = () => { > Open Source - + + + + + + + + + Converter + + + Convert JSON to YAML, CSV to JSON, YAML to XML, and more. + + + + + + + Generate Types + + + Generate TypeScript types, Golang structs, Rust serde, and more. + + + + + + + JSON Schema + + + Generate JSON schema from JSON data. + + + Generate JSON data from JSON schema. + + + + + + + + + + + + JSON + {jsonError ? : } + + + setJson(value || "")} + onValidate={errors => setJsonError(!!errors.length)} + language="json" + height={500} + options={editorOptions} + onMount={(_editor, monaco) => (monacoRef.current = monaco)} + /> + + + + + JSON Schema + {jsonSchemaError ? : } + + + setJsonSchema(value || "")} + onValidate={errors => setJsonSchemaError(!!errors.length)} + language="json" + height={500} + options={editorOptions} + /> + + + + + ); +}; + +export default JSONSchemaTool; diff --git a/src/pages/type/csv-to-go.tsx b/src/pages/type/csv-to-go.tsx new file mode 100644 index 0000000..9da125d --- /dev/null +++ b/src/pages/type/csv-to-go.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { TypegenWrapper } from "src/containers/TypeLayout/TypegenWrapper"; +import { FileFormat, TypeLanguage } from "src/enums/file.enum"; + +const TypePage = () => { + return ; +}; + +export default TypePage; diff --git a/src/pages/type/csv-to-kotlin.tsx b/src/pages/type/csv-to-kotlin.tsx new file mode 100644 index 0000000..128954f --- /dev/null +++ b/src/pages/type/csv-to-kotlin.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { TypegenWrapper } from "src/containers/TypeLayout/TypegenWrapper"; +import { FileFormat, TypeLanguage } from "src/enums/file.enum"; + +const TypePage = () => { + return ; +}; + +export default TypePage; diff --git a/src/pages/type/csv-to-rust-serde.tsx b/src/pages/type/csv-to-rust-serde.tsx new file mode 100644 index 0000000..6bbf0a2 --- /dev/null +++ b/src/pages/type/csv-to-rust-serde.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { TypegenWrapper } from "src/containers/TypeLayout/TypegenWrapper"; +import { FileFormat, TypeLanguage } from "src/enums/file.enum"; + +const TypePage = () => { + return ; +}; + +export default TypePage; diff --git a/src/pages/type/csv-to-typescript.tsx b/src/pages/type/csv-to-typescript.tsx new file mode 100644 index 0000000..c43b92e --- /dev/null +++ b/src/pages/type/csv-to-typescript.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { TypegenWrapper } from "src/containers/TypeLayout/TypegenWrapper"; +import { FileFormat, TypeLanguage } from "src/enums/file.enum"; + +const TypePage = () => { + return ; +}; + +export default TypePage; diff --git a/src/pages/type/json-to-go.tsx b/src/pages/type/json-to-go.tsx new file mode 100644 index 0000000..9e8c9e4 --- /dev/null +++ b/src/pages/type/json-to-go.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { TypegenWrapper } from "src/containers/TypeLayout/TypegenWrapper"; +import { FileFormat, TypeLanguage } from "src/enums/file.enum"; + +const TypePage = () => { + return ; +}; + +export default TypePage; diff --git a/src/pages/type/json-to-kotlin.tsx b/src/pages/type/json-to-kotlin.tsx new file mode 100644 index 0000000..a1be479 --- /dev/null +++ b/src/pages/type/json-to-kotlin.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { TypegenWrapper } from "src/containers/TypeLayout/TypegenWrapper"; +import { FileFormat, TypeLanguage } from "src/enums/file.enum"; + +const TypePage = () => { + return ; +}; + +export default TypePage; diff --git a/src/pages/type/json-to-rust-serde.tsx b/src/pages/type/json-to-rust-serde.tsx new file mode 100644 index 0000000..9cafb3c --- /dev/null +++ b/src/pages/type/json-to-rust-serde.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { TypegenWrapper } from "src/containers/TypeLayout/TypegenWrapper"; +import { FileFormat, TypeLanguage } from "src/enums/file.enum"; + +const TypePage = () => { + return ; +}; + +export default TypePage; diff --git a/src/pages/type/json-to-typescript.tsx b/src/pages/type/json-to-typescript.tsx new file mode 100644 index 0000000..c35f51f --- /dev/null +++ b/src/pages/type/json-to-typescript.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { TypegenWrapper } from "src/containers/TypeLayout/TypegenWrapper"; +import { FileFormat, TypeLanguage } from "src/enums/file.enum"; + +const TypePage = () => { + return ; +}; + +export default TypePage; diff --git a/src/pages/type/xml-to-go.tsx b/src/pages/type/xml-to-go.tsx new file mode 100644 index 0000000..9a3fd19 --- /dev/null +++ b/src/pages/type/xml-to-go.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { TypegenWrapper } from "src/containers/TypeLayout/TypegenWrapper"; +import { FileFormat, TypeLanguage } from "src/enums/file.enum"; + +const TypePage = () => { + return ; +}; + +export default TypePage; diff --git a/src/pages/type/xml-to-kotlin.tsx b/src/pages/type/xml-to-kotlin.tsx new file mode 100644 index 0000000..540f1b1 --- /dev/null +++ b/src/pages/type/xml-to-kotlin.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { TypegenWrapper } from "src/containers/TypeLayout/TypegenWrapper"; +import { FileFormat, TypeLanguage } from "src/enums/file.enum"; + +const TypePage = () => { + return ; +}; + +export default TypePage; diff --git a/src/pages/type/xml-to-rust-serde.tsx b/src/pages/type/xml-to-rust-serde.tsx new file mode 100644 index 0000000..03804b2 --- /dev/null +++ b/src/pages/type/xml-to-rust-serde.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { TypegenWrapper } from "src/containers/TypeLayout/TypegenWrapper"; +import { FileFormat, TypeLanguage } from "src/enums/file.enum"; + +const TypePage = () => { + return ; +}; + +export default TypePage; diff --git a/src/pages/type/xml-to-typescript.tsx b/src/pages/type/xml-to-typescript.tsx new file mode 100644 index 0000000..a4a0020 --- /dev/null +++ b/src/pages/type/xml-to-typescript.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { TypegenWrapper } from "src/containers/TypeLayout/TypegenWrapper"; +import { FileFormat, TypeLanguage } from "src/enums/file.enum"; + +const TypePage = () => { + return ; +}; + +export default TypePage; diff --git a/src/pages/type/yaml-to-go.tsx b/src/pages/type/yaml-to-go.tsx new file mode 100644 index 0000000..ec0956b --- /dev/null +++ b/src/pages/type/yaml-to-go.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { TypegenWrapper } from "src/containers/TypeLayout/TypegenWrapper"; +import { FileFormat, TypeLanguage } from "src/enums/file.enum"; + +const TypePage = () => { + return ; +}; + +export default TypePage; diff --git a/src/pages/type/yaml-to-kotlin.tsx b/src/pages/type/yaml-to-kotlin.tsx new file mode 100644 index 0000000..99c2fae --- /dev/null +++ b/src/pages/type/yaml-to-kotlin.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { TypegenWrapper } from "src/containers/TypeLayout/TypegenWrapper"; +import { FileFormat, TypeLanguage } from "src/enums/file.enum"; + +const TypePage = () => { + return ; +}; + +export default TypePage; diff --git a/src/pages/type/yaml-to-rust-serde.tsx b/src/pages/type/yaml-to-rust-serde.tsx new file mode 100644 index 0000000..e54e0fc --- /dev/null +++ b/src/pages/type/yaml-to-rust-serde.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { TypegenWrapper } from "src/containers/TypeLayout/TypegenWrapper"; +import { FileFormat, TypeLanguage } from "src/enums/file.enum"; + +const TypePage = () => { + return ; +}; + +export default TypePage; diff --git a/src/pages/type/yaml-to-typescript.tsx b/src/pages/type/yaml-to-typescript.tsx new file mode 100644 index 0000000..5af7c04 --- /dev/null +++ b/src/pages/type/yaml-to-typescript.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { TypegenWrapper } from "src/containers/TypeLayout/TypegenWrapper"; +import { FileFormat, TypeLanguage } from "src/enums/file.enum"; + +const TypePage = () => { + return ; +}; + +export default TypePage;