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


Top comments (0)