JSONCrack Codebase Analysis — Part 4.3 — LiveEditor
Tags: webdev, javascript, opensource, nextjs
Published: 2024-01-08 20:24:37

[jsoncrack](https://jsoncrack.com/editor) is a popular opensource tool used to visualise json into a mindmap. It is built using Next.js.

We, at [TThroo](https://tthroo.com/), 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](https://dev.to/tthroo/jsoncrack-codebase-analysis-part-41-editor-panes-component-26lg) has 2 main components.

- JsonEditor Component

[Part 4.2.1 — Editor — JsonEditor Component](https://dev.to/tthroo/jsoncrack-codebase-analysis-part-421-editor-jsoneditor-component-2ef8)
and 

[Part 4.2.1.1 — JsonEditor — debouncedUpdateJson](https://dev.to/tthroo/jsoncrack-codebase-analysis-part-4211-jsoneditor-debouncedupdatejson-cld)

- LiveEditor Component

In this article, let's understand how [LiveEditor](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/containers/Editor/LiveEditor.tsx) 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](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/containers/Editor/LiveEditor.tsx#L72)

```
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 off zustand to simplify the state management. [useConfig](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/store/useConfig.ts) is a zustand store.

![View mode](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vbllbyx5io9ot0v2742d.png)

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

![Graphs File Placement](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zzh79fsgkhf9hp8wstjj.png)

No wonder [Graph](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/containers/Views/GraphView/index.tsx) 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](https://reaflow.dev/?path=/story/demos-basic--simple) 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](https://dev.to/tthroo/jsoncrack-codebase-analysis-part-4211-jsoneditor-debouncedupdatejson-cld)

`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