JSONCrack Codebase Analysis - Part 4.3 - LiveEditor
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
Subscribe to my newsletter
Read articles from TThroo directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by