├── .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 | json-preview logo 3 | 4 |

JSON Preview

5 |

Convert JSON files to downloadable diagrams

6 |
7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
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 | change direction icon diagram 21 |
22 |
setDownloadCount(downloadCount + 1)}> 23 | download diagram 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 | clear icon 16 |
17 |
18 |
19 | 20 | linkedin icon 21 | 22 | 23 | twitter icon 24 | 25 | 26 | github icon 27 | 28 | 29 | star icon 30 | 31 |
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 | --------------------------------------------------------------------------------