JSONCrack Codebase Analysis - Part 4.3 - LiveEditor

TThrooTThroo
4 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.

Part 4.1 — Editor — Panes Component has 2 main components.

JsonEditor Component

Part 4.2.1 — Editor — JsonEditor Component
and Part 4.2.1.1 — JsonEditor — debouncedUpdateJson

LiveEditor Component

In this article, let’s understand how LiveEditor works.

const LiveEditor: React.FC = () => {
  const [contextOpened, setContextOpened] = React.useState(false);
  const [contextPosition, setContextPosition] = React.useState({
    x: 0,
    y: 0,
  });

return (
    <StyledLiveEditor
      onContextMenuCapture={e => {
        e.preventDefault();
        setContextOpened(true);
        setContextPosition({ x: e.pageX, y: e.pageY });
      }}
      onClick={() => setContextOpened(false)}
    >
      <div
        style={{
          position: "fixed",
          top: contextPosition.y,
          left: contextPosition.x,
          zIndex: 100,
        }}
      >
        <Menu opened={false} shadow="sm">
          <Menu.Dropdown>
            <Menu.Item>
              <Text size="xs">Download as Image</Text>
            </Menu.Item>
            <Menu.Item>
              <Text size="xs">Zoom to Fit</Text>
            </Menu.Item>
            <Menu.Item>
              <Text size="xs">Rotate</Text>
            </Menu.Item>
          </Menu.Dropdown>
        </Menu>
      </div>
      <View />
    </StyledLiveEditor>
  );
};

Hang on minute? where is the LiveEditor? On the first look, it might be apparent but what are interested in is here: View

const View = () => {
  const viewMode = useConfig(state => state.viewMode);
  if (viewMode === ViewMode.Graph) return <Graph />;
  if (viewMode === ViewMode.Tree) return <TreeView />;
  return null;
};

By this time, you must have understood the power of zustand to simplify the state management. useConfig is a zustand store.

A view mode that is header aka Toolbar is used to set the view type. You guessed it. We are now moving away from LiveEditor to Graph.

A rookie mistake here would be dump all the Graph and TreeView code in the same file, this only makes your life hard when it comes to maintenance.

Graph is in src/containers/Views/GraphView. Can the containers folder have Views? It it makes sense, so be it. Because this Views contains Graph and TreeView are in a way containers.

There is no strict rule to follow to place your files, you be the best judge and place them where it makes sense. You can move the files around as your project grows in size.

Graph

No wonder Graph is placed in containers/Views. It just has more modularity to it.

return (
    <>
      <Loading loading={loading} message="Painting graph..." />
      <StyledEditorWrapper
        $widget={isWidget}
        onContextMenu={e => e.preventDefault()}
        onClick={blurOnClick}
        key={String(gesturesEnabled)}
        $showRulers={rulersEnabled}
        {...bindLongPress()}
      >
        <Space
          onCreate={setViewPort}
          onContextMenu={e => e.preventDefault()}
          treatTwoFingerTrackPadGesturesLikeTouch={gesturesEnabled}
          pollForElementResizing
        >
          <GraphCanvas isWidget={isWidget} />
        </Space>
      </StyledEditorWrapper>
    </>
  );

Graph to GraphCanvas, Notice how the component we are interested is now changed GraphCanvas.

But why GraphCanvas? It is because Reaflow uses Canvas to render visualisations. Context matters here. Hence the name Canvas appended to GraphCanvas

const GraphCanvas = ({ isWidget }: GraphProps) => {
  const { validateHiddenNodes } = useToggleHide();
  const setLoading = useGraph(state => state.setLoading);
  const centerView = useGraph(state => state.centerView);
  const direction = useGraph(state => state.direction);
  const nodes = useGraph(state => state.nodes);
  const edges = useGraph(state => state.edges);
  const [paneWidth, setPaneWidth] = React.useState(2000);
  const [paneHeight, setPaneHeight] = React.useState(2000);
  const onLayoutChange = React.useCallback(
    (layout: ElkRoot) => {
      if (layout.width && layout.height) {
        const areaSize = layout.width * layout.height;
        const changeRatio = Math.abs((areaSize * 100) / (paneWidth * paneHeight) - 100);
        setPaneWidth(layout.width + 50);
        setPaneHeight((layout.height as number) + 50);
        setTimeout(() => {
          validateHiddenNodes();
          window.requestAnimationFrame(() => {
            if (changeRatio > 70 || isWidget) centerView();
            setLoading(false);
          });
        });
      }
    },
    [isWidget, paneHeight, paneWidth, centerView, setLoading, validateHiddenNodes]
  );
  return (
    <Canvas
      className="jsoncrack-canvas"
      onLayoutChange={onLayoutChange}
      node={p => <CustomNode {...p} />}
      edge={p => <CustomEdge {...p} />}
      nodes={nodes}
      edges={edges}
      maxHeight={paneHeight}
      maxWidth={paneWidth}
      height={paneHeight}
      width={paneWidth}
      direction={direction}
      layoutOptions={layoutOptions}
      key={direction}
      pannable={false}
      zoomable={false}
      animated={false}
      readonly={true}
      dragEdge={null}
      dragNode={null}
      fit={true}
    />
  );
};

Remember when we were looking to understanding what was responsible for generating nodes and edges in Part 4.2.1.1 — JsonEditor — debouncedUpdateJson

GraphCanvas uses nodes and edges to render the visualisation.

This is how what you entered as a json in code editor visualised using Reaflow in the LiveEditor.

I just love how efficiently props management is done using zustand.

Conclusion

We navigated between few files to understand the workings for LiveEditor and saw how powerful zustand can be when it comes to state management.

Part 5 would be about Toolbar and Bottombar.

Part 6 — How the payments and subscription is integrated into this product using lemonsqueezy.

Thank you for reading till the end. If you have any questions or need help with a 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