jsoncrack.com is a popular opensource tool used to visualise json into a mindmap. It is built using Next.js.
We, at TThroo, love open source and perform codebase analysis on popular repositories, document, and provide a detailed explanation of the codebase. This enables OSS enthusiasts to learn and contribute to open source projects, and we also apply these learnings in our projects.
In this post, let’s understand the editor functionality used in jsoncrack.com.
Open src/pages/editor.tsx in a new tab and let’s take the top down approach. Let’s first look into the components used.
<EditorWrapper>
<StyledEditorWrapper>
<Head>
<title>Editor | JSON Crack</title>
{hasQuery && <meta name="robots" content="noindex,nofollow" />}
</Head>
<StyledPageWrapper>
<Toolbar />
<StyledEditorWrapper>
<Panes />
</StyledEditorWrapper>
</StyledPageWrapper>
<BottomBar />
</StyledEditorWrapper>
</EditorWrapper>
EditorWrapper
This is the first component used in page loaded when you visit https://jsoncrack.com/editor and contains the following
export const EditorWrapper: React.FC<{
children: React.ReactNode;
}> = ({ children }) => {
const darkmodeEnabled = useConfig(state => state.darkmodeEnabled);
return (
<ThemeProvider theme={darkmodeEnabled ? darkTheme : lightTheme}>
<MantineProvider forceColorScheme={darkmodeEnabled ? "dark" : "light"}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</MantineProvider>
</ThemeProvider>
);
};
Providers are imported at the top of the file.
import { MantineProvider } from "@mantine/core";
import { ThemeProvider } from "styled-components";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
Toolbar
Toolbar is what you see as header/navigation on top of the page
The code is as below:
<Styles.StyledTools>
{isWidget && <Logo />}
{!isWidget && (
<Group gap="xs" justify="left" w="100%" style={{ flexWrap: "nowrap" }}>
<Styles.StyledToolElement title="JSON Crack">
<Flex gap="xs" align="center" justify="center">
<JSONCrackLogo fontSize="1.2em" />
</Flex>
</Styles.StyledToolElement>
<Select
defaultValue="json"
size="xs"
value={format}
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" },
]}
/>
<ViewModeMenu />
<Styles.StyledToolElement title="Import File" onClick={() => setVisible("import")(true)}>
Import
</Styles.StyledToolElement>
<ViewMenu />
<ToolsMenu />
<Styles.StyledToolElement title="Cloud" onClick={() => setVisible("cloud")(true)}>
Cloud
</Styles.StyledToolElement>
<Styles.StyledToolElement title="Download as File" onClick={handleSave}>
Download
</Styles.StyledToolElement>
</Group>
)}
<Group gap="xs" justify="right" w="100%" style={{ flexWrap: "nowrap" }}>
{!premium && !isWidget && (
<Styles.StyledToolElement onClick={() => setVisible("premium")(true)}>
<Text display="flex" c="teal" fz="xs" fw="bold" style={{ textAlign: "center", gap: 4 }}>
<MdWorkspacePremium size="18" />
Get Premium
</Text>
</Styles.StyledToolElement>
)}
<SearchInput />
{!isWidget && (
<>
<Styles.StyledToolElement
title="Save as Image"
onClick={() => setVisible("download")(true)}
>
<FiDownload size="18" />
</Styles.StyledToolElement>
<ZoomMenu />
<AccountMenu />
<OptionsMenu />
<Styles.StyledToolElement
title="Fullscreen"
$hide={isWidget}
onClick={fullscreenBrowser}
>
<AiOutlineFullscreen size="18" />
</Styles.StyledToolElement>
</>
)}
</Group>
</Styles.StyledTools>
);
};
Code above is self explanatory, the options that you see in the toolbar are listed here.
Panes
Panes comprise of two main components, JsonEditor and LiveEditor. I thought why not name this to LivePreview, but then I realised you could edit the mindmap from the livepreview — this is only possible when you are on paid plan, well it isn’t exactly a preview anymore. is it? Yup, naming conventions and their meaning matter. Seasoned devs have constructive discourse about variable or function naming conventions.
“Hang on a minute, what do you mean constructive discourse? appendText() appends a random text and returns the updated string.” Right mate, shouldn’t it be appendAndGetText() or even better, write two functions, one for append and one for get. This is just an example. Anyways, let’s get back to the code now.
<StyledEditor proportionalLayout={false}>
<Allotment.Pane
preferredSize={450}
minSize={fullscreen ? 0 : 300}
maxSize={800}
visible={!fullscreen}
>
<JsonEditor />
</Allotment.Pane>
<Allotment.Pane minSize={0}>
<LiveEditor />
</Allotment.Pane>
</StyledEditor>
BottomBar
BottomBar has features like Valid, Save to cloud, Private, Share, Live Transform etc.,
<StyledBottomBar>
{data?.name && (
<Head>
<title>{data.name} | JSON Crack</title>
</Head>
)}
<StyledLeft>
<StyledBottomBarItem onClick={toggleEditor}>
<BiSolidDockLeft />
</StyledBottomBarItem>
{fileName && (
<StyledBottomBarItem onClick={() => setVisible("cloud")(true)}>
<VscSourceControl />
{fileName}
</StyledBottomBarItem>
)}
<StyledBottomBarItem>
{error ? (
<Popover width="auto" shadow="md" position="top" withArrow>
<Popover.Target>
<Flex align="center" gap={2}>
<VscError color="red" size={16} />
<Text c="red" fw={500} fz="xs">
Invalid
</Text>
</Flex>
</Popover.Target>
<Popover.Dropdown
style={{
pointerEvents: "none",
}}
>
<Text size="xs">{error}</Text>
</Popover.Dropdown>
</Popover>
) : (
<Flex align="center" gap={2}>
<MdOutlineCheckCircleOutline />
<Text size="xs">Valid</Text>
</Flex>
)}
</StyledBottomBarItem>
{(data?.owner_email === user?.email || (!data && user)) && (
<StyledBottomBarItem onClick={handleSaveJson} disabled={isUpdating || error}>
{hasChanges || !user ? <AiOutlineCloudUpload /> : <AiOutlineCloudSync />}
{hasChanges || !user ? (query?.json ? "Unsaved Changes" : "Save to Cloud") : "Saved"}
</StyledBottomBarItem>
)}
{data?.owner_email === user?.email && (
<StyledBottomBarItem onClick={setPrivate} disabled={isUpdating}>
{isPrivate ? <AiOutlineLock /> : <AiOutlineUnlock />}
{isPrivate ? "Private" : "Public"}
</StyledBottomBarItem>
)}
<StyledBottomBarItem
onClick={() => setVisible("share")(true)}
disabled={isPrivate || !data}
>
<AiOutlineLink />
Share
</StyledBottomBarItem>
{liveTransformEnabled ? (
<StyledBottomBarItem onClick={() => toggleLiveTransform(false)}>
<VscSync />
<Text fz="xs">Live Transform</Text>
</StyledBottomBarItem>
) : (
<StyledBottomBarItem onClick={() => toggleLiveTransform(true)}>
<VscSyncIgnored />
<Text fz="xs">Manual Transform</Text>
</StyledBottomBarItem>
)}
{!liveTransformEnabled && (
<StyledBottomBarItem onClick={() => setContents({})}>
<TbTransform />
Transform
</StyledBottomBarItem>
)}
</StyledLeft>
<StyledRight>
<StyledBottomBarItem>Nodes: {nodeCount}</StyledBottomBarItem>
<StyledBottomBarItem onClick={() => setVisible("review")(true)}>
<VscFeedback />
Feedback
</StyledBottomBarItem>
</StyledRight>
</StyledBottomBar>
There is a lot happening in the above code, but don’t worry. I will explain each of these components in detail in the upcoming tutorials.
Conclusion
We looked at 3 main components in Editor for jsoncrack.com, Toolbar, Panes, BottomBar. In the upcoming tutorials, I will pick each of these components and go into details such as custom hooks used and how this application zustand configured. If you have any questions, feel free to email us at ram@tthroo.com.
Top comments (0)