JSONCrack Codebase Analysis - Part 5 - Toolbar and Bottom bar

TThrooTThroo
6 min read

jsoncrack 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 part 5, we understand how Toolbar and BottomBar are configured.

Interested to learn from opensource?

I am going to write a tutorial series where we use what we learnt from this jsoncrack codebase analysis, potentially involves usage of monaco editor, mantine, authentication and zustand configuration and modals, pretty much everything that is worth applying.

Before You ask, no, it is not going to be a jsoncrack clone but a unique project that uses similar codebase and similar approach and configuration to what we have seen in this series. It involves using supabase, zustand, mantine, monaco editor, nextjs.

Join the waitlist and I will send you the link to the tutorials once they are ready.

Where are they used?

They are used in Editor component. Part 4 explains about Editor component.

Toolbar

You will find Toolbar inside src/containers/Toolbar.

export const Toolbar: React.FC<{ isWidget?: boolean }> = ({ isWidget = false }) => {
  const getJson = useJson(state => state.getJson);
  const setVisible = useModal(state => state.setVisible);
  const setFormat = useFile(state => state.setFormat);
  const format = useFile(state => state.format);
  const premium = useUser(state => state.premium);
  const handleSave = () => {
    const a = document.createElement("a");
    const file = new Blob([getJson()], { type: "text/plain" });
    a.href = window.URL.createObjectURL(file);
    a.download = "jsoncrack.json";
    a.click();
  };
  return (
    <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>
  );
};

isWidget flag is used to show either logo or the below optiions.

Select

<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" },
  ]}
/>

It is a Select component from mantine

import { Flex, Group, Select, Text } from "@mantine/core";

onChange sets format in useFile store

ViewMode

ViewMode deserves a file with its own html and is a standalone that directly updates zustand store.

Import

Import sets visible prop in useModal store.

ViewMenu

ViewMenu has a lot of changes to update useGraph store that is directly correlated to the way visualisation is rendered using Reaflow.

And this is again in it own file under Toolbar folder because it has additional HTML that is not the right fit to be in Toolbar/index.tsx

The same approach above is applied to ToolsMenu.

Cloud

Cloud triggers a modal by setting visible prop in useModal store.

Modals are handled super neatly.
Took me a while to figure this one out.

If you open _app.tsx, You will find ModalController

const ModalController = () => {
  const setVisible = useModal(state => state.setVisible);
  const modalStates = useModal(state => modalComponents.map(modal => state[modal.key]));
  return (
      <EditorWrapper>
        {modalComponents.map(({ key, component }, index) => {
          const ModalComponent = component;
          const opened = modalStates[index];
          return <ModalComponent key={key} opened={opened} onClose={() => setVisible(key)(false)} />;
        })}
      </EditorWrapper>
    );
};

That is basically telling to render what is set to true in useModal store. All the Modals used are placed in a container specifically dedicated for Modals. How NEAT!.

This is one of the way cleanest ways to deal with Modals especially when you have a lot of them :)

You can follow through similarly to understand the other items available in Toolbar.

Fullscreen Bar Icon

function fullscreenBrowser() {
  if (!document.fullscreenElement) {
    document.documentElement.requestFullscreen().catch(() => {
      toast.error("Unable to enter fullscreen mode.");
    });
  } else if (document.exitFullscreen) {
    document.exitFullscreen();
  }
}

Next time, You are looking for a piece of code to toggle in and out of a full screenmode, You now know how!

BottomBar

BottomBar does not contain as many options as Topbar.

Interestingly, Bottombar is part of Editor, instead of it being a separate folder due to its limited number of options/features.

<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>

The code is pretty straigt forward, modifies what needs to be set in stores and few flags are to display what’s needed.

If You ask me to refactor the above code, I would choose to move the following to separate component.

{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>
        )}

In this new file, You could do the following:

const liveTransformEnabled = useConfig(state => state.liveTransformEnabled);

This could improve the readability, again, it is choice and not hard set rule, depends on dev teams and their preferences. You cannot over engineer at the same time, it is a fine balance. How you draw this fine balance is directly correlated to code maintainability and readability.

Conclusion

We learnt how to use zustand stores and perform operations and do the state updates.

Modal configuration was interesting, I have never seen it before. All the modals are placed inside a folder and a separate store for modals. You can leverage this pattern if your project is dealing with modals heavy operations to handle and maintain the code easily.

Up next, we have part 6 where we talk about how the payments and subscription is integrated into this product using lemonsqueezy.

If you have any questions or need help with your project, feel free to reach out to me at ram@tthroo.com

0
Subscribe to my newsletter

Read articles from TThroo directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

TThroo
TThroo