├── .github
└── workflows
│ └── azure-static-web-apps-gray-grass-0ca72d810.yml
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── index.html
├── logo.png
├── manifest.json
└── robots.txt
└── src
├── App.css
├── App.js
├── App.test.js
├── Layout
├── Layout.js
└── Layout.module.scss
├── assets
├── icons
│ ├── icons8-clear-64.png
│ ├── icons8-direction-64.png
│ ├── icons8-down-arrow-64 (1).png
│ ├── icons8-down-arrow-64.png
│ ├── icons8-download-50.png
│ ├── icons8-github.svg
│ ├── icons8-linkedin-2.svg
│ ├── icons8-right-arrow-64 (1).png
│ ├── icons8-right-arrow-64.png
│ ├── icons8-star-50.png
│ └── icons8-twitter.svg
└── images
│ └── default-removebg-preview.png
├── components
├── CodeEditor
│ ├── CodeEditor.js
│ └── CodeEditor.module.scss
├── NavBar
│ ├── NavBar.js
│ └── NavBar.module.scss
├── PreviewBoard
│ ├── PreviewBoard.js
│ ├── PreviewBoard.module.scss
│ ├── index.css
│ └── nodes.js
├── Sidebar
│ ├── Sidebar.js
│ └── Sidebar.module.scss
└── View
│ ├── View.js
│ └── View.module.scss
├── index.css
├── index.js
├── logo.svg
├── reportWebVitals.js
├── routes
└── index.js
└── setupTests.js
/.github/workflows/azure-static-web-apps-gray-grass-0ca72d810.yml:
--------------------------------------------------------------------------------
1 | name: Azure Static Web Apps CI/CD
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | types: [opened, synchronize, reopened, closed]
9 | branches:
10 | - main
11 |
12 | jobs:
13 | build_and_deploy_job:
14 | if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
15 | runs-on: ubuntu-latest
16 | name: Build and Deploy Job
17 | steps:
18 | - uses: actions/checkout@v2
19 | with:
20 | submodules: true
21 | - name: Build And Deploy
22 | id: builddeploy
23 | uses: Azure/static-web-apps-deploy@v1
24 | with:
25 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_GRAY_GRASS_0CA72D810 }}
26 | repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
27 | action: "upload"
28 | ###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
29 | # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
30 | app_location: "/" # App source code path
31 | api_location: "" # Api source code path - optional
32 | output_location: "build" # Built app content directory - optional
33 | ###### End of Repository/Build Configurations ######
34 |
35 | close_pull_request_job:
36 | if: github.event_name == 'pull_request' && github.event.action == 'closed'
37 | runs-on: ubuntu-latest
38 | name: Close Pull Request Job
39 | steps:
40 | - name: Close Pull Request
41 | id: closepullrequest
42 | uses: Azure/static-web-apps-deploy@v1
43 | with:
44 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_GRAY_GRASS_0CA72D810 }}
45 | action: "close"
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 |
JSON Preview
5 |
Convert JSON files to downloadable diagrams
6 |
7 |
8 |
19 |
20 | ## Overview
21 |
22 | A web application which allows a user to easily visualize any JSON file and generate diagrams from it.
23 |
24 |
25 | Live Preview:
26 |
27 | ### Tools and Packages Used
28 |
29 | - React
30 | - prismjs
31 | - react-component-export-image
32 | - react-simple-code-editor
33 | - reactflow
34 | - sass
35 |
36 | ## Features
37 |
38 | - Tabs
39 | - Preview Board
40 | - Code editor
41 | - Zoom In/out
42 | - Focus control
43 | - Lock
44 | - Download Button
45 |
46 | ## Why I started this project
47 |
48 | I came across a code editor that helps you visualize JSON file, so I thought about build something similar as a web app with additional features.
49 |
50 | I had a challenges with generating the diagram, but `Reactflow` gave me insight on how to connect the different nodes and edges together.
51 |
52 | ## How It Works
53 |
54 | Users can easily interact with the code editor and type in JSON structured code.
55 |
56 | The code editor has a *debugging feature* - red indicator that shows error exist in the code. Thus, It can't be previewed until it is fixed.
57 |
58 | At the bottom-left, it has features which allows:
59 |
60 | - **Zoom in or out** from the workspace.
61 | - **Fit to view** for easy switching on the vertical or horizontal direction.
62 | - **Lock** to restrict the diagram generated from further edit or distortion.
63 |
64 | At the top-right, there is:
65 |
66 | - **Download** to the diagrams
67 | - **Orientation adjustment** button for custom view.
68 |
69 |
70 | The left side menu consists of:
71 |
72 | - **Eraser** to delete the contents in the code editor.
73 | - **Social media handles** (Linkedin, Twitter)
74 | - **Github repository**
75 |
76 | ## Installation
77 |
78 | 1. Clone the repository
79 |
80 | ```bash
81 | git clone https://github.com/ugwustanley/json-preview.git
82 | ```
83 |
84 | 2. Change the working directory
85 |
86 | ```bash
87 | cd json-preview
88 | ```
89 |
90 | 3. Install dependencies
91 |
92 | ```bash
93 | npm install
94 | ```
95 |
96 | 4. Run the app
97 |
98 | ```bash
99 | npm start
100 | ```
101 |
102 | You can now see it at `http://localhost:3000` in your browser.
103 |
104 | ## Contributing
105 |
106 | If you face any problems while using the application, please open an issue or submit a pull request.
107 |
108 | ## Support
109 |
110 | Please give this project a star⭐, if you like it.
111 |
112 | ## License
113 |
114 | This project is built under the [MIT License](./License).
115 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "json-preview",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.5",
7 | "@testing-library/react": "^13.4.0",
8 | "@testing-library/user-event": "^13.5.0",
9 | "dagre": "^0.8.5",
10 | "phosphor-react": "^1.4.1",
11 | "prismjs": "^1.29.0",
12 | "react": "^18.2.0",
13 | "react-component-export-image": "^1.0.6",
14 | "react-dom": "^18.2.0",
15 | "react-flow-renderer": "^10.3.17",
16 | "react-router-dom": "^6.4.1",
17 | "react-scripts": "5.0.1",
18 | "react-simple-code-editor": "^0.13.1",
19 | "reactflow": "^11.0.0",
20 | "sass": "^1.55.0",
21 | "web-vitals": "^2.1.4"
22 | },
23 | "scripts": {
24 | "start": "react-scripts start",
25 | "build": "react-scripts build",
26 | "test": "react-scripts test",
27 | "eject": "react-scripts eject"
28 | },
29 | "eslintConfig": {
30 | "extends": [
31 | "react-app",
32 | "react-app/jest"
33 | ]
34 | },
35 | "browserslist": {
36 | "production": [
37 | ">0.2%",
38 | "not dead",
39 | "not op_mini all"
40 | ],
41 | "development": [
42 | "last 1 chrome version",
43 | "last 1 firefox version",
44 | "last 1 safari version"
45 | ]
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Json Preview
20 |
21 |
22 |
23 |
24 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ugwustanley/json-preview/773fbd2d7b80c391f01323284a12fd7046d95ef7/public/logo.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Json Preview",
3 | "name": "Json Preview",
4 | "icons": [
5 | {
6 | "src": "logo.png",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#011627",
24 | "background_color": "tomato"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ugwustanley/json-preview/773fbd2d7b80c391f01323284a12fd7046d95ef7/src/App.css
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import {useState} from 'react'
2 | import {BrowserRouter} from 'react-router-dom'
3 | import Routes from './routes'
4 |
5 | function App() {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default App;
14 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/src/Layout/Layout.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import styles from "./Layout.module.scss";
3 | import Sidebar from "../components/Sidebar/Sidebar";
4 | import NavBar from "../components/NavBar/NavBar";
5 | import View from "../components/View/View";
6 |
7 | const Layout = ({ children }) => {
8 | const [layout, setLayout] = useState("LR");
9 | const [downloadCount, setDownloadCount] = useState(0);
10 | const [code, setCode] = useState(`
11 | {
12 | "name": "json-preview",
13 | "version": "0.1.0",
14 | "private": true,
15 |
16 | "scripts": {
17 | "start": "react-scripts start",
18 | "build": "react-scripts build",
19 | "test": "react-scripts test",
20 | "eject": "react-scripts eject"
21 | },
22 |
23 | "browserslist": {
24 |
25 | "production": [
26 | ">0.2%",
27 | "not dead",
28 | "not op_mini all"
29 | ]
30 |
31 | }
32 | }
33 |
34 |
35 | `);
36 |
37 | return (
38 |
39 |
40 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
58 |
59 |
60 |
61 | );
62 | };
63 |
64 | export default Layout;
65 |
--------------------------------------------------------------------------------
/src/Layout/Layout.module.scss:
--------------------------------------------------------------------------------
1 | .layout{
2 | font-family: 'Noto Sans Mono', monospace;
3 | width: 100vw;
4 | height: 100vh;
5 | overflow: hidden;
6 |
7 | &__main{
8 | display: grid;
9 | grid-template-columns: 60px 1fr;
10 | grid-template-rows: 1fr;
11 | height: calc(100vh - 50px)
12 | // height: 70%;
13 | }
14 | &__sidebar{
15 | width: 100%;
16 | height: 100%;
17 | }
18 | &__body{
19 | width: 100%;
20 | height: 100%;
21 | }
22 | }
--------------------------------------------------------------------------------
/src/assets/icons/icons8-clear-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ugwustanley/json-preview/773fbd2d7b80c391f01323284a12fd7046d95ef7/src/assets/icons/icons8-clear-64.png
--------------------------------------------------------------------------------
/src/assets/icons/icons8-direction-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ugwustanley/json-preview/773fbd2d7b80c391f01323284a12fd7046d95ef7/src/assets/icons/icons8-direction-64.png
--------------------------------------------------------------------------------
/src/assets/icons/icons8-down-arrow-64 (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ugwustanley/json-preview/773fbd2d7b80c391f01323284a12fd7046d95ef7/src/assets/icons/icons8-down-arrow-64 (1).png
--------------------------------------------------------------------------------
/src/assets/icons/icons8-down-arrow-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ugwustanley/json-preview/773fbd2d7b80c391f01323284a12fd7046d95ef7/src/assets/icons/icons8-down-arrow-64.png
--------------------------------------------------------------------------------
/src/assets/icons/icons8-download-50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ugwustanley/json-preview/773fbd2d7b80c391f01323284a12fd7046d95ef7/src/assets/icons/icons8-download-50.png
--------------------------------------------------------------------------------
/src/assets/icons/icons8-github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/icons8-linkedin-2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/icons8-right-arrow-64 (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ugwustanley/json-preview/773fbd2d7b80c391f01323284a12fd7046d95ef7/src/assets/icons/icons8-right-arrow-64 (1).png
--------------------------------------------------------------------------------
/src/assets/icons/icons8-right-arrow-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ugwustanley/json-preview/773fbd2d7b80c391f01323284a12fd7046d95ef7/src/assets/icons/icons8-right-arrow-64.png
--------------------------------------------------------------------------------
/src/assets/icons/icons8-star-50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ugwustanley/json-preview/773fbd2d7b80c391f01323284a12fd7046d95ef7/src/assets/icons/icons8-star-50.png
--------------------------------------------------------------------------------
/src/assets/icons/icons8-twitter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/default-removebg-preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ugwustanley/json-preview/773fbd2d7b80c391f01323284a12fd7046d95ef7/src/assets/images/default-removebg-preview.png
--------------------------------------------------------------------------------
/src/components/CodeEditor/CodeEditor.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Editor from 'react-simple-code-editor';
3 | import { highlight, languages } from 'prismjs/components/prism-core';
4 | import 'prismjs/components/prism-clike';
5 | import 'prismjs/components/prism-javascript';
6 | import 'prismjs/themes/prism.css';
7 | import styles from './CodeEditor.module.scss'
8 |
9 | function CodeEditor({code , setCode}) {
10 |
11 | return (
12 |
13 | setCode(code)}
16 | highlight={code => highlight(code, languages.js)}
17 | padding={10}
18 | style={{
19 | fontFamily: " 'Noto Sans Mono', monospace ",
20 | fontSize: 14,
21 | background: '#011627',
22 | }}
23 | />
24 |
25 | );
26 | }
27 |
28 | export default CodeEditor;
--------------------------------------------------------------------------------
/src/components/CodeEditor/CodeEditor.module.scss:
--------------------------------------------------------------------------------
1 | .codeEditor {
2 | width: 100%;
3 | height: calc(100vh - 70px);
4 | background: #011627;
5 | color: #fff;
6 | overflow-y: auto;
7 |
8 | ::-webkit-scrollbar-track {
9 | border: 0px solid black;
10 | background-color: transparent;
11 | //background-color: #F5F5F5;
12 | }
13 |
14 | ::-webkit-scrollbar {
15 | width: 8px;
16 | height: 8px;
17 | background-color: transparent;
18 | @media (max-width: 480px) {
19 | width: 5px;
20 | height: 5px;
21 | }
22 | // margin-right:10rem;
23 | // background-color: #F5F5F5;
24 | }
25 |
26 | ::-webkit-scrollbar-thumb {
27 | background-color: rgba(0, 0, 0, 0.2);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/NavBar/NavBar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DownloadSimple, ArrowRight, ArrowDown } from "phosphor-react";
3 | import DownloadIcon from '../../assets/icons/icons8-download-50.png'
4 | import DirectionIcon from '../../assets/icons/icons8-direction-64.png'
5 | import ArrowRightIcon from '../../assets/icons/icons8-right-arrow-64 (1).png'
6 | import ArrowDownIcon from '../../assets/icons/icons8-down-arrow-64 (1).png'
7 | import Logo from '../../assets/images/default-removebg-preview.png'
8 | import styles from "./NavBar.module.scss";
9 |
10 | const NavBar = ({setLayout, layout , setDownloadCount , downloadCount}) => {
11 | return (
12 |
13 |
14 |

15 |
16 |
17 |
18 |
19 |
setLayout(layout === "LR" ? "TB" : "LR")}>
20 |

21 |
22 |
setDownloadCount(downloadCount + 1)}>
23 |

24 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default NavBar;
31 |
--------------------------------------------------------------------------------
/src/components/NavBar/NavBar.module.scss:
--------------------------------------------------------------------------------
1 | .navbar{
2 | width: 100%;
3 | height: 40px;
4 | border-bottom: 1px solid #122d42cb;
5 | // #219fd54b;
6 | background: #011627;
7 | color: #fff;
8 | display: flex;
9 | align-items: center;
10 | justify-content: space-between;
11 | &__right{
12 |
13 | margin-left: -.7rem;
14 |
15 | img{
16 | height: 180px;
17 | }
18 | }
19 | &__left{
20 | margin-top: .3rem;
21 | margin-right: 2rem;
22 | display: flex;
23 | align-items: center;
24 | @media (max-width: 480px){
25 | margin-right: 1rem;
26 | }
27 | & *{
28 | margin-left: .7rem;
29 | @media (max-width: 480px){
30 | margin-left: .4rem;
31 | }
32 | cursor: pointer;
33 | &:hover{
34 | background: #011627bb;
35 | }
36 | &:last-child{
37 | img{
38 | width: 21px;
39 | margin-bottom: .2rem;
40 | }
41 | }
42 | }
43 | img{
44 | width: 20px;
45 |
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/src/components/PreviewBoard/PreviewBoard.js:
--------------------------------------------------------------------------------
1 | import React, { useCallback, forwardRef , useEffect } from "react";
2 | import ReactFlow, {
3 | addEdge,
4 | ConnectionLineType,
5 | useNodesState,
6 | useEdgesState,
7 | Controls,
8 | Background,
9 | ReactFlowProvider,
10 | } from "react-flow-renderer";
11 | import dagre from "dagre";
12 |
13 | import Nodes from "./nodes";
14 |
15 | import "./index.css";
16 |
17 | const LayoutFlow = forwardRef(({ layout, code }, ref) => {
18 | const nodeInstance = new Nodes(typeof code === "string" ? JSON.parse(code) : {});
19 |
20 | const [newNodes, newEdges] = nodeInstance.getNodes();
21 |
22 | const dagreGraph = new dagre.graphlib.Graph();
23 | dagreGraph.setDefaultEdgeLabel(() => ({}));
24 |
25 | const nodeWidth = 272;
26 | const nodeHeight = 76;
27 |
28 |
29 |
30 | const getLayoutedElements = (nodes, edges, direction = "TB") => {
31 | const isHorizontal = direction === "LR";
32 | dagreGraph.setGraph({ rankdir: direction });
33 |
34 | nodes.forEach((node) => {
35 | dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
36 | });
37 |
38 | edges.forEach((edge) => {
39 | dagreGraph.setEdge(edge.source, edge.target);
40 | });
41 |
42 | dagre.layout(dagreGraph);
43 |
44 | nodes.forEach((node) => {
45 | const nodeWithPosition = dagreGraph.node(node.id);
46 | node.targetPosition = isHorizontal ? "left" : "top";
47 | node.sourcePosition = isHorizontal ? "right" : "bottom";
48 |
49 | // We are shifting the dagre node position (anchor=center center) to the top left
50 | // so it matches the React Flow node anchor point (top left).
51 | node.position = {
52 | x: nodeWithPosition.x - nodeWidth / 2,
53 | y: nodeWithPosition.y - nodeHeight / 2,
54 | };
55 |
56 |
57 |
58 | return node;
59 | });
60 |
61 | return { nodes, edges };
62 | };
63 |
64 | const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(
65 | newNodes,
66 | newEdges
67 | );
68 |
69 | const [nodes, setNodes, onNodesChange] = useNodesState(layoutedNodes);
70 | const [edges, setEdges, onEdgesChange] = useEdgesState(layoutedEdges);
71 |
72 | const onConnect = useCallback(
73 | (params) =>
74 | setEdges((eds) =>
75 | addEdge(
76 | { ...params, type: ConnectionLineType.SmoothStep, animated: true },
77 | eds
78 | )
79 | ),
80 | []
81 | );
82 | const onLayout = useCallback(
83 | (direction) => {
84 | const { nodes: layoutedNodes, edges: layoutedEdges } =
85 | getLayoutedElements(nodes, edges, direction);
86 |
87 | setNodes([...layoutedNodes]);
88 | setEdges([...layoutedEdges]);
89 | },
90 | [nodes, edges]
91 | );
92 |
93 |
94 |
95 | useEffect(() => {
96 | if (layout) {
97 | onLayout(layout);
98 | console.log("called ", layout);
99 | }
100 |
101 | }, [layout]);
102 |
103 | return (
104 |
109 |
110 |
111 |
120 |
121 |
122 |
123 |
124 |
125 | );
126 | });
127 |
128 | export default LayoutFlow;
129 |
--------------------------------------------------------------------------------
/src/components/PreviewBoard/PreviewBoard.module.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ugwustanley/json-preview/773fbd2d7b80c391f01323284a12fd7046d95ef7/src/components/PreviewBoard/PreviewBoard.module.scss
--------------------------------------------------------------------------------
/src/components/PreviewBoard/index.css:
--------------------------------------------------------------------------------
1 | .layoutflow {
2 | flex-grow: 1;
3 | position: relative;
4 | height: 100%;
5 | font-family: 'Noto Sans Mono', monospace;
6 | }
7 |
8 | .layoutflow .controls {
9 | position: absolute;
10 | right: 10px;
11 | top: 10px;
12 | z-index: 10;
13 | font-size: 12px;
14 | }
15 |
16 | .layoutflow .controls button:first-child {
17 | margin-right: 10px;
18 | }
--------------------------------------------------------------------------------
/src/components/PreviewBoard/nodes.js:
--------------------------------------------------------------------------------
1 | class Nodes {
2 | constructor(json) {
3 | this.json = json;
4 | this.position = { x: 0, y: 0 };
5 | this.nodes = [];
6 | this.edges = [];
7 | this.edgeType = "default";
8 | this.animated = false;
9 | }
10 |
11 | getValueStyle(value) {
12 | if (typeof value === "string") {
13 | return { color: "#fff", padding: 0, margin: 0 };
14 | }
15 | if (typeof value === "number") {
16 | return { color: "tomato", padding: 0, margin: 0 };
17 | }
18 | if (typeof value === "boolean") {
19 | return { color: "yellow", padding: 0, margin: 0 };
20 | }
21 | }
22 | getNodes(input = this.json, parentID = "11", name = "Root") {
23 | if (parentID === "11") {
24 | this.nodes.push({
25 | id: "11",
26 | data: {
27 | label: (
28 |
29 | {`./`}
37 | Root
38 |
39 | ),
40 | },
41 | position: this.position,
42 | style: {
43 | background: "#122d424f",
44 | fontSize: ".95rem",
45 | textAlign: "left",
46 | color: "teal",
47 | border: "2px solid #122d42cb",
48 | minWidth: 220,
49 | padding: 0,
50 | textOverflow: "ellipsis !important",
51 | whiteSpace: "nowrap",
52 | overflow: "hidden",
53 | },
54 | });
55 | }
56 | if (typeof input === "object" && !Array.isArray(input)) {
57 | let valueArray = [];
58 | Object.keys(input).map((key, index) => {
59 | if (typeof input[key] === "object" || Array.isArray(input[key])) {
60 | const id = `${parentID + 1}${index + 1} ${Math.random() * 10000}`;
61 |
62 | this.nodes.push({
63 | id,
64 | data: {
65 | label: (
66 |
67 |
75 | {Array.isArray(input[key]) ? `[]` : `{}`}
76 |
77 |
88 | {key}
89 |
90 |
91 | ),
92 | },
93 | position: this.position,
94 | style: {
95 | background: "#122d424f",
96 | fontSize: ".95rem",
97 | textAlign: "left",
98 | color: "teal",
99 | border: "2px solid #122d42cb",
100 | minWidth: 280,
101 | padding: 0,
102 | textOverflow: "ellipsis",
103 | whiteSpace: "nowrap",
104 | overflow: "hidden",
105 | },
106 | });
107 |
108 | this.edges.push({
109 | id: id + parentID,
110 | source: parentID,
111 | target: id,
112 | type: this.edgeType,
113 | animated: this.animated,
114 | });
115 |
116 | this.getNodes(input[key], id, key);
117 | } else {
118 | valueArray.push({ key: key, value: input[key] });
119 | }
120 | });
121 |
122 | if (valueArray.length > 0) {
123 | const id = `${Math.random() & 1000000}${Math.random() * 100000}`;
124 |
125 | this.nodes.push({
126 | id,
127 | data: {
128 | label: (
129 |
130 | {valueArray.map((value) => (
131 |
143 | {value.key}:
144 | {`${value?.value}`}
147 |
148 | ))}
149 |
150 | ),
151 | },
152 | position: this.position,
153 | style: {
154 | background: "#122d424f",
155 | fontSize: ".95rem",
156 | fontFamily: " 'Noto Sans Mono', monospace",
157 | textAlign: "left",
158 | color: "teal",
159 | border: "2px solid #122d42cb",
160 | width: 280,
161 | padding: 0,
162 | },
163 | });
164 |
165 | this.edges.push({
166 | id: id + parentID,
167 | source: parentID,
168 | target: id,
169 | type: this.edgeType,
170 | animated: this.animated,
171 | });
172 | }
173 | }
174 |
175 | if (Array.isArray(input)) {
176 | input.map((item, index) => {
177 | if (typeof item === "object" || Array.isArray(item)) {
178 | const id = `${parentID + 1}${index + 1} ${Math.random() * 10000}`;
179 |
180 | this.getNodes(item, parentID);
181 | } else {
182 | const id = `${parentID + 1}${index + 1} ${Math.random() * 10000}`;
183 |
184 | this.nodes.push({
185 | id,
186 | data: {
187 | label: (
188 |
189 |
203 | {item}
204 |
205 |
206 | ),
207 | },
208 | position: this.position,
209 | style: {
210 | background: "#122d424f",
211 | fontSize: ".95rem",
212 | fontFamily: "'Noto Sans Mono', monospace",
213 | textAlign: "left",
214 | color: "teal",
215 | border: "2px solid #122d42cb",
216 | width: 280,
217 | padding: 0,
218 | },
219 | });
220 |
221 | this.edges.push({
222 | id: id + parentID,
223 | source: parentID,
224 | target: id,
225 | type: this.edgeType,
226 | animated: this.animated,
227 | });
228 | }
229 | });
230 | }
231 |
232 | return [this.nodes, this.edges];
233 | }
234 | }
235 |
236 | export default Nodes;
237 |
238 | //export default Nodes;
239 |
--------------------------------------------------------------------------------
/src/components/Sidebar/Sidebar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { GitPullRequest, TwitterLogo } from "phosphor-react";
3 | import TwitterIcon from '../../assets/icons/icons8-twitter.svg'
4 | import GithubIcon from '../../assets/icons/icons8-github.svg'
5 | import LinkedinIcon from '../../assets/icons/icons8-linkedin-2.svg'
6 | import StarIcon from '../../assets/icons/icons8-star-50.png'
7 | import ClearIcon from '../../assets/icons/icons8-clear-64.png'
8 | import styles from "./Sidebar.module.scss";
9 |
10 | const Sidebar = ({setCode}) => {
11 | return (
12 |
13 |
14 |
setCode(`{}`)}>
15 |

16 |
17 |
18 |
32 |
33 | );
34 | };
35 |
36 | export default Sidebar;
37 |
--------------------------------------------------------------------------------
/src/components/Sidebar/Sidebar.module.scss:
--------------------------------------------------------------------------------
1 | .sidebar{
2 | background: #011627;
3 | border-right: 1px solid #122d42cb;
4 | width: 60px;
5 | height: calc(100vh - 40px);
6 | color: #fff;
7 | box-sizing: border-box;
8 | display: flex;
9 | justify-content: space-between;
10 | align-items: center;
11 | flex-direction: column;
12 | &__top{
13 | width: fit-content;
14 | height: fit-content;
15 | display: flex;
16 | align-self: baseline;
17 | justify-content: center;
18 | flex-direction: column;
19 | margin:0 auto;
20 | margin-top: 2rem;
21 |
22 | & *{
23 | margin-bottom: 0.7rem;
24 | }
25 | img{
26 | width: 25px;
27 | cursor: pointer;
28 | }
29 | }
30 | &__bottom{
31 | width: fit-content;
32 | height: fit-content;
33 | display: flex;
34 | align-self: baseline;
35 | justify-content: center;
36 | flex-direction: column;
37 | margin:0 auto;
38 | margin-bottom: 2rem;
39 |
40 | & *{
41 | margin-top: 0.7rem;
42 | }
43 | img{
44 | width: 20px;
45 | cursor: pointer;
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/src/components/View/View.js:
--------------------------------------------------------------------------------
1 | import React, { useState , createRef , useRef , useEffect } from "react";
2 | import { exportComponentAsJPEG, exportComponentAsPDF, exportComponentAsPNG } from 'react-component-export-image';
3 | import { WarningCircle } from "phosphor-react";
4 |
5 | //components
6 | import PreviewBoard from "../PreviewBoard/PreviewBoard";
7 | import CodeEditor from "../CodeEditor/CodeEditor";
8 |
9 | //stylesheet
10 | import styles from "./View.module.scss";
11 |
12 | const View = ({ layout, code, setCode , downloadCount }) => {
13 | const [activeTab, setActiveTab] = useState("preview-board");
14 | const [isJson , setIsJson] = useState(false)
15 | const diagramRef = useRef()
16 |
17 | const isJSONString = (str) => {
18 | try {
19 | JSON.parse(str);
20 | } catch (e) {
21 | return false;
22 | }
23 | console.log("called")
24 | return true;
25 | };
26 |
27 | useEffect(() => {
28 | setIsJson(isJSONString(code))
29 | }, [code])
30 |
31 | useEffect(() => {
32 | if(downloadCount > 0){
33 | exportComponentAsPNG(diagramRef)
34 | }
35 | }, [downloadCount])
36 |
37 |
38 | return (
39 |
40 |
41 |
setActiveTab("code-editor")}
48 | >
49 | Code Editor {isJson ? null : }
50 |
51 |
isJson && setActiveTab("preview-board")}
58 | >
59 | Preview Board
60 |
61 |
62 |
63 | {activeTab === "code-editor" ? (
64 |
65 | ) : (
66 |
67 | )}
68 |
69 | );
70 | };
71 |
72 | export default View;
73 |
--------------------------------------------------------------------------------
/src/components/View/View.module.scss:
--------------------------------------------------------------------------------
1 | .view {
2 | font-family: "Noto Sans Mono", monospace;
3 | width: 100%;
4 | height: 100%;
5 | ::-webkit-scrollbar-track {
6 | border: 0px solid black;
7 | background-color: transparent;
8 | }
9 |
10 | ::-webkit-scrollbar {
11 | width: 8px;
12 | height: 8px;
13 | background-color: transparent;
14 | @media (max-width: 480px) {
15 | width: 5px;
16 | height: 5px;
17 | }
18 | }
19 |
20 | ::-webkit-scrollbar-thumb {
21 | background-color: #0b2942;
22 | }
23 | &__tabControls {
24 | display: flex;
25 | height: 30px;
26 | background: #041323;
27 | border-bottom: 1px solid #122d42cb;
28 | }
29 | &__tabControl {
30 | background: transparent;
31 | height: 100%;
32 | min-width: 140px;
33 | max-width: 200px;
34 | display: flex;
35 | align-items: center;
36 | box-sizing: border-box;
37 | padding: 0 1rem;
38 | font-size: 0.8rem;
39 | cursor: pointer;
40 | color: #549efc;
41 | @media (max-width: 480px){
42 | width: calc(50% - 20px);
43 | }
44 | &__active {
45 | // background: #020d18;
46 | background: #0b2942;
47 | color: #549efc;
48 | border-top: 1px solid #219fd5;
49 | border-bottom: 1px solid #219fd5;
50 | }
51 | span {
52 | margin-left: 0.5rem;
53 | margin-top: 0.4rem;
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | const root = ReactDOM.createRoot(document.getElementById('root'));
8 | root.render(
9 |
10 |
11 |
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/routes/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Route, Routes } from "react-router-dom";
3 |
4 | //components
5 | import Layout from "../Layout/Layout";
6 |
7 | const index = () => {
8 | return (
9 |
10 | } />
11 |
12 | );
13 | };
14 |
15 | export default index;
16 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------