JSONCrack Codebase Analysis — Part 4 — Editor

·

4 min read

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.

jsoncrack editor

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

Toolbar

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.