Repository: AykutSarac/jsoncrack.com Branch: main Commit: 73a51d3bb295 Files: 203 Total size: 349.7 KB Directory structure: gitextract_0apmokt3/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ └── feature_request.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── deploy.yml │ └── pull-request.yml ├── .gitignore ├── .npmrc ├── .vscode/ │ ├── launch.json │ └── tasks.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── apps/ │ ├── vscode/ │ │ ├── .gitignore │ │ ├── .prettierignore │ │ ├── .prettierrc │ │ ├── .vscodeignore │ │ ├── LICENSE.md │ │ ├── README.md │ │ ├── esbuild.config.mjs │ │ ├── eslint.config.mjs │ │ ├── ext-src/ │ │ │ ├── extension.ts │ │ │ └── webview.ts │ │ ├── index.html │ │ ├── package.json │ │ ├── src/ │ │ │ ├── App.tsx │ │ │ ├── components/ │ │ │ │ └── NodeModal.tsx │ │ │ ├── env.d.ts │ │ │ ├── global.css │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ └── vite.config.ts │ └── www/ │ ├── .dockerignore │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc │ ├── Dockerfile │ ├── LICENSE.md │ ├── docker-compose.yml │ ├── eslint.config.mjs │ ├── next-env.d.ts │ ├── next-sitemap.config.js │ ├── next.config.js │ ├── nginx.conf │ ├── package.json │ ├── public/ │ │ ├── .nojekyll │ │ ├── CNAME │ │ ├── manifest.json │ │ ├── robots.txt │ │ ├── sitemap-0.xml │ │ └── sitemap.xml │ ├── shims/ │ │ └── empty.ts │ ├── src/ │ │ ├── constants/ │ │ │ ├── globalStyle.ts │ │ │ ├── graph.ts │ │ │ ├── seo.ts │ │ │ └── theme.ts │ │ ├── data/ │ │ │ ├── example.json │ │ │ ├── faq.json │ │ │ ├── privacy.json │ │ │ └── terms.json │ │ ├── enums/ │ │ │ ├── file.enum.ts │ │ │ └── viewMode.enum.ts │ │ ├── features/ │ │ │ ├── Banner.tsx │ │ │ ├── editor/ │ │ │ │ ├── BottomBar.tsx │ │ │ │ ├── ExternalMode.tsx │ │ │ │ ├── FullscreenDropzone.tsx │ │ │ │ ├── LiveEditor.tsx │ │ │ │ ├── TextEditor.tsx │ │ │ │ ├── Toolbar/ │ │ │ │ │ ├── FileMenu.tsx │ │ │ │ │ ├── SearchInput.tsx │ │ │ │ │ ├── ThemeToggle.tsx │ │ │ │ │ ├── ToolsMenu.tsx │ │ │ │ │ ├── ViewMenu.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styles.ts │ │ │ │ └── views/ │ │ │ │ ├── GraphView/ │ │ │ │ │ ├── CustomEdge/ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── CustomNode/ │ │ │ │ │ │ ├── ObjectNode.tsx │ │ │ │ │ │ ├── TextNode.tsx │ │ │ │ │ │ ├── TextRenderer.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── styles.tsx │ │ │ │ │ ├── NotSupported.tsx │ │ │ │ │ ├── OptionsMenu.tsx │ │ │ │ │ ├── SecureInfo.tsx │ │ │ │ │ ├── ZoomControl.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── lib/ │ │ │ │ │ │ ├── jsonParser.ts │ │ │ │ │ │ └── utils/ │ │ │ │ │ │ ├── calculateNodeSize.ts │ │ │ │ │ │ ├── getChildrenEdges.ts │ │ │ │ │ │ └── getOutgoers.ts │ │ │ │ │ └── stores/ │ │ │ │ │ └── useGraph.ts │ │ │ │ └── TreeView/ │ │ │ │ ├── Label.tsx │ │ │ │ ├── Value.tsx │ │ │ │ └── index.tsx │ │ │ └── modals/ │ │ │ ├── DownloadModal/ │ │ │ │ └── index.tsx │ │ │ ├── ImportModal/ │ │ │ │ └── index.tsx │ │ │ ├── JPathModal/ │ │ │ │ └── index.tsx │ │ │ ├── JQModal/ │ │ │ │ └── index.tsx │ │ │ ├── ModalController.tsx │ │ │ ├── NodeModal/ │ │ │ │ └── index.tsx │ │ │ ├── SchemaModal/ │ │ │ │ └── index.tsx │ │ │ ├── TypeModal/ │ │ │ │ └── index.tsx │ │ │ ├── index.ts │ │ │ └── modalTypes.ts │ │ ├── hooks/ │ │ │ ├── useFocusNode.ts │ │ │ └── useJsonQuery.ts │ │ ├── layout/ │ │ │ ├── ConverterLayout/ │ │ │ │ ├── PageLinks.tsx │ │ │ │ ├── ToolPage.tsx │ │ │ │ └── options.ts │ │ │ ├── JSONCrackBrandLogo.tsx │ │ │ ├── Landing/ │ │ │ │ ├── FAQ.tsx │ │ │ │ ├── Features.tsx │ │ │ │ ├── HeroPreview.tsx │ │ │ │ ├── HeroSection.tsx │ │ │ │ ├── Section1.tsx │ │ │ │ ├── Section2.tsx │ │ │ │ └── Section3.tsx │ │ │ ├── PageLayout/ │ │ │ │ ├── Footer.tsx │ │ │ │ ├── Navbar.tsx │ │ │ │ └── index.tsx │ │ │ └── TypeLayout/ │ │ │ ├── PageLinks.tsx │ │ │ └── TypegenWrapper.tsx │ │ ├── lib/ │ │ │ └── utils/ │ │ │ ├── generateType.ts │ │ │ ├── helpers.ts │ │ │ ├── json2go.js │ │ │ ├── jsonAdapter.ts │ │ │ ├── mantineColorScheme.ts │ │ │ └── search.ts │ │ ├── pages/ │ │ │ ├── 404.tsx │ │ │ ├── _app.tsx │ │ │ ├── _document.tsx │ │ │ ├── _error.tsx │ │ │ ├── converter/ │ │ │ │ ├── csv-to-json.tsx │ │ │ │ ├── csv-to-xml.tsx │ │ │ │ ├── csv-to-yaml.tsx │ │ │ │ ├── json-to-csv.tsx │ │ │ │ ├── json-to-xml.tsx │ │ │ │ ├── json-to-yaml.tsx │ │ │ │ ├── xml-to-csv.tsx │ │ │ │ ├── xml-to-json.tsx │ │ │ │ ├── xml-to-yaml.tsx │ │ │ │ ├── yaml-to-csv.tsx │ │ │ │ ├── yaml-to-json.tsx │ │ │ │ └── yaml-to-xml.tsx │ │ │ ├── docs.tsx │ │ │ ├── editor.tsx │ │ │ ├── index.tsx │ │ │ ├── legal/ │ │ │ │ ├── privacy.tsx │ │ │ │ └── terms.tsx │ │ │ ├── tools/ │ │ │ │ └── json-schema.tsx │ │ │ ├── type/ │ │ │ │ ├── csv-to-go.tsx │ │ │ │ ├── csv-to-kotlin.tsx │ │ │ │ ├── csv-to-rust.tsx │ │ │ │ ├── csv-to-typescript.tsx │ │ │ │ ├── json-to-go.tsx │ │ │ │ ├── json-to-kotlin.tsx │ │ │ │ ├── json-to-rust.tsx │ │ │ │ ├── json-to-typescript.tsx │ │ │ │ ├── xml-to-go.tsx │ │ │ │ ├── xml-to-kotlin.tsx │ │ │ │ ├── xml-to-rust.tsx │ │ │ │ ├── xml-to-typescript.tsx │ │ │ │ ├── yaml-to-go.tsx │ │ │ │ ├── yaml-to-kotlin.tsx │ │ │ │ ├── yaml-to-rust.tsx │ │ │ │ └── yaml-to-typescript.tsx │ │ │ └── widget.tsx │ │ ├── store/ │ │ │ ├── useConfig.ts │ │ │ ├── useFile.ts │ │ │ ├── useJson.ts │ │ │ └── useModal.ts │ │ └── types/ │ │ └── styled.d.ts │ └── tsconfig.json ├── package.json ├── packages/ │ └── jsoncrack-react/ │ ├── .gitignore │ ├── .prettierrc │ ├── LICENSE.md │ ├── README.md │ ├── eslint.config.mjs │ ├── package.json │ ├── src/ │ │ ├── JSONCrackComponent.tsx │ │ ├── JSONCrackStyles.module.css │ │ ├── components/ │ │ │ ├── Controls.module.css │ │ │ ├── Controls.tsx │ │ │ ├── CustomEdge.tsx │ │ │ ├── CustomNode.tsx │ │ │ ├── Node.module.css │ │ │ ├── ObjectNode.tsx │ │ │ ├── TextNode.tsx │ │ │ ├── TextRenderer.module.css │ │ │ ├── TextRenderer.tsx │ │ │ └── nodeStyles.ts │ │ ├── css-modules.d.ts │ │ ├── index.ts │ │ ├── parser.ts │ │ ├── theme.ts │ │ ├── types.ts │ │ └── utils/ │ │ └── calculateNodeSize.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.ts ├── pnpm-workspace.yaml └── turbo.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: Bug report description: Create a report to help us improve title: "[BUG]: " labels: bug body: - type: textarea id: description attributes: label: Issue description description: | Describe the issue in as much detail as possible. Tip: You can attach images or log files by clicking this area to highlight it and then dragging files into it. placeholder: | Steps to reproduce with below code sample: 1. do thing 2. click... 3. observe behavior 4. see error logs below validations: required: true - type: textarea id: media attributes: label: Media & Screenshots description: Include screenshots or video of reproduction as much as possible - type: textarea id: os attributes: label: Operating system description: Which OS does your application run on? value: | - OS: [e.g. iOS]: - Browser [e.g. chrome, safari]: - Any other details... - type: dropdown id: priority attributes: label: Priority this issue should have description: Please be realistic. If you need to elaborate on your reasoning, please use the Issue description field above. options: - Low (slightly annoying) - Medium (should be fixed soon) - High (immediate attention needed) validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yml ================================================ name: Feature request description: Request a new feature labels: [feature] body: - type: textarea id: description attributes: label: Feature description: A clear and concise description of what the problem is, or what feature you want to be implemented. placeholder: I'm always frustrated when..., Discord has recently released..., A good addition would be... validations: required: true - type: textarea id: alternatives attributes: label: Alternative solutions or implementations description: A clear and concise description of any alternative solutions or features you have considered. - type: textarea id: additional-context attributes: label: Other context description: Any other context, screenshots, or file uploads that help us understand your feature request. ================================================ FILE: .github/pull_request_template.md ================================================ ## Issue Closes #[ISSUE_NUMBER] ## What Changed Brief description of what you changed and why. Example: - Added JSON validation tooltip on parse error - Improved performance by memoizing graph component - Fixed bug where large JSON files caused slowdown ## How to Test Step-by-step instructions to test the change: 1. ... 2. ... 3. ... ## Evidence **Screenshots or Video Required** — Choose one or both: ### Screenshots - [ ] Before/after screenshots attached (for UI changes) ### Video - [ ] Screen recording of the feature in action ### Testing - [ ] Tested locally with `pnpm dev` - [ ] No console errors - [ ] Tested with large JSON files (if applicable) ## Performance Impact Any notes on performance, re-renders, or potential concerns? - [ ] No performance impact - [ ] Improved performance (explain how) - [ ] Potential impact (explain and justify) ## Additional Notes Anything else reviewers should know? --- **Remember:** If this PR is not linked to an issue, it may be closed. Always reference an approved issue! ================================================ FILE: .github/workflows/deploy.yml ================================================ name: Deploy Next.js site to Pages on: push: branches: ["main"] workflow_dispatch: permissions: contents: read pages: write id-token: write concurrency: group: "pages" cancel-in-progress: true jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 with: fetch-depth: 0 - name: Setup Node uses: actions/setup-node@v6 with: node-version: "24.10.0" - uses: pnpm/action-setup@v4 name: Install pnpm with: version: 10.20.0 run_install: false - name: Get pnpm store directory shell: bash run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - uses: actions/cache@v5 name: Setup pnpm cache with: path: ${{ env.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Restore Turbo and Next.js cache uses: actions/cache@v5 with: path: | .turbo apps/www/.next/cache key: ${{ runner.os }}-turbo-next-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-${{ hashFiles('apps/www/**.[jt]s', 'apps/www/**.[jt]sx', 'packages/**.[jt]s', 'packages/**.[jt]sx') }} restore-keys: | ${{ runner.os }}-turbo-next-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}- - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build with Turborepo run: pnpm run build:www env: NEXT_PUBLIC_GA_MEASUREMENT_ID: ${{ vars.NEXT_PUBLIC_GA_MEASUREMENT_ID }} - name: Upload Build Artifacts uses: actions/upload-artifact@v6 with: name: build-files path: ./apps/www/out/_next/static/chunks - name: Upload artifact uses: actions/upload-pages-artifact@v4 with: path: './apps/www/out' # Deployment job deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: build steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 ================================================ FILE: .github/workflows/pull-request.yml ================================================ name: Verify Pull Request on: pull_request: branches: [ "main" ] jobs: cache-and-install: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 - name: Install Node.js uses: actions/setup-node@v6 with: node-version: "24.10.0" - uses: pnpm/action-setup@v4 name: Install pnpm with: version: 10.20.0 run_install: false - name: Get pnpm store directory shell: bash run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - uses: actions/cache@v5 name: Setup pnpm cache with: path: ${{ env.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Restore Turbo and Next.js cache uses: actions/cache@v5 with: path: | .turbo apps/www/.next/cache key: ${{ runner.os }}-turbo-next-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-${{ hashFiles('apps/www/**.[jt]s', 'apps/www/**.[jt]sx', 'packages/**.[jt]s', 'packages/**.[jt]sx') }} restore-keys: | ${{ runner.os }}-turbo-next-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}- - name: Install dependencies run: pnpm install --frozen-lockfile - name: Lint run: pnpm turbo run lint - name: Build run: pnpm turbo run build ================================================ FILE: .gitignore ================================================ # Agent tooling .agents .claude .codex skills-lock.json # Package manager node_modules/ .npm-cache/ .pnpm-store/ .pnp* npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* # Turborepo .turbo/ # Build outputs and caches (all workspaces) **/.next/ **/out/ **/coverage/ **/*.tsbuildinfo # OS .DS_Store ================================================ FILE: .npmrc ================================================ engine-strict=true ================================================ FILE: .vscode/launch.json ================================================ { "version": "0.2.0", "configurations": [ { "name": "Run VSCode Extension", "type": "extensionHost", "request": "launch", "args": ["--extensionDevelopmentPath=${workspaceFolder}/apps/vscode"], "outFiles": ["${workspaceFolder}/apps/vscode/build/**/*.js"], "preLaunchTask": "build vscode extension" } ] } ================================================ FILE: .vscode/tasks.json ================================================ { "version": "2.0.0", "tasks": [ { "label": "build vscode extension", "type": "shell", "command": "pnpm run build:vscode", "group": { "kind": "build", "isDefault": true } }, { "label": "watch vscode extension", "type": "shell", "command": "pnpm run dev:vscode", "isBackground": true } ] } ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at opensource@github.com. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to JSON Crack Thank you for wanting to contribute! This is a community-driven project, and we appreciate your help. Please read this guide carefully to make the review process smooth and fast. **Read our [Code of Conduct](./CODE_OF_CONDUCT.md) first** — we want to keep this community friendly and welcoming. --- ## Before You Start: The Issue-First Workflow **Always open or find an issue BEFORE you start coding.** This saves everyone time. 1. **Check existing issues** — Search to see if someone already reported this or is working on it 2. **Open a new issue** if one doesn't exist — Describe what you want to fix or build 3. **Wait for approval** — I'll review and give feedback (usually within a few days) 4. **Once approved**, you can start coding 5. **Link your PR to the issue** — Use `Closes #123` in your PR description This workflow prevents duplicate work and ensures your contribution aligns with the project's direction. --- ## Quick Setup ### Prerequisites - Node.js 18+ - pnpm (or npm/yarn) ### Tech Stack JSON Crack uses: - **React** — UI library - **Reaflow** — Graph visualization - **Mantine UI** — UI components - **Zustand** — State management ### Get Started ```bash # Clone the repo git clone https://github.com/AykutSarac/jsoncrack.com.git cd jsoncrack.com # Install dependencies pnpm install # Run the dev server pnpm dev ``` The app will be available at `http://localhost:3000` --- ## How to Submit a Pull Request ### Requirements Before submitting, make sure your PR includes: 1. **Issue ID** — Reference the issue: `Closes #123` 2. **Clear description** — What does this change do? Why? 3. **Evidence of working changes** — One or both: - **Screenshot** — Show the UI before/after - **Video** — Screen recording of the feature in action 4. **Test it locally** — Run `pnpm dev` and verify it works 5. **Follow code style** — Use [Google TypeScript Style Guide](https://google.github.io/styleguide/tsguide.html) ### Creating Your Branch ```bash git checkout -b fix/issue-123-description # or git checkout -b feature/issue-123-description ``` Use clear branch names that reference the issue. --- ## Guidelines ### Performance First - Avoid unnecessary re-renders - Use React DevTools Profiler to check performance - Test with large JSON files to ensure no slowdowns ### Code Quality - Follow the [Google TypeScript Style Guide](https://google.github.io/styleguide/tsguide.html) - Write descriptive commit messages - Keep changes focused — one feature/fix per PR ### Testing - Manually test your changes thoroughly - Describe exactly how you tested it in the PR - Make sure existing features still work --- ## Example PR Here's what a good PR looks like: **Title:** Add JSON validation tooltip on parse error **Description:** ``` Closes #234 ## What Changed Added a helpful tooltip that shows validation errors when JSON fails to parse, making it easier for users to fix their JSON. ## How to Test 1. Paste invalid JSON: `{invalid` 2. Look for the red error indicator 3. Hover over it to see the detailed error message ## Evidence - [Screenshot of tooltip](link-to-image) ## Performance Notes No performance impact. Tooltip renders conditionally only on errors. ``` --- ## Questions? - Found a bug? Open an issue - Have an idea? Open an issue - Confused about something? Comment on the issue Thank you for contributing to JSON Crack! 🎉 ================================================ FILE: LICENSE.md ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2025 Aykut Saraç Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================

Logo

JSON Crack

The open-source JSON Editor.
Learn more »

ToDiagram · Discord · Website · Issues · VS Code

## About the Project booking-screen ## Visualize JSON into interactive graphs JSON Crack is a tool for visualizing JSON data in a structured, interactive graphs, making it easier to explore, format, and validate JSON. It offers features like converting JSON to other formats (CSV, YAML), generating JSON Schema, executing queries, and exporting visualizations as images. Designed for both readability and usability. * **Visualizer**: Instantly convert JSON, YAML, CSV, and XML into interactive graphs or trees in dark or light mode. * **Convert**: Seamlessly transform data formats, like JSON to CSV or XML to JSON, for easy sharing. * **Format & Validate**: Beautify and validate JSON, YAML, and CSV for clear and accurate data. * **Code Generation**: Generate TypeScript interfaces, Golang structs, and JSON Schema. * **JSON Schema**: Create JSON Schema, mock data, and validate various data formats. * **Advanced Tools**: Decode JWT, randomize data, and run jq or JSON path queries. * **Export Image**: Download your visualization as PNG, JPEG, or SVG. * **Privacy**: All data processing is local; nothing is stored on our servers. ## Recognition Featured on Hacker News JSON Crack | Product Hunt ## Integrations - [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=AykutSarac.jsoncrack-vscode) - [npm Package (`jsoncrack-react`)](https://www.npmjs.com/package/jsoncrack-react) ## Contributing - Found a bug or missing feature? Open an issue on [GitHub Issues](https://github.com/AykutSarac/jsoncrack.com/issues). - Want to contribute code or docs? Start with our [contribution guide](./CONTRIBUTING.md). ## Sponsors & Support If you find JSON Crack useful, you can support the project by using [ToDiagram](https://todiagram.com). ## Stay Up-to-Date JSON Crack officially launched as v1.0 on the 17th of February 2022 and we've come a long way so far. Watch **releases** of this repository to be notified of future updates: Star at GitHub ## Getting Started To get a local copy up and running, please follow these simple steps. ### Prerequisites Here is what you need to be able to run JSON Crack. - Node.js (Version: >=24.x) - pnpm (Version: >=10) ## Development ### Setup 1. Clone the repo into a public GitHub repository (or fork https://github.com/AykutSarac/jsoncrack.com/fork). If you plan to distribute the code, read the [`LICENSE`](/LICENSE.md) for additional details. ```sh git clone https://github.com/AykutSarac/jsoncrack.com.git ``` 2. Go to the project folder ```sh cd jsoncrack.com ``` 3. Install packages ```sh pnpm install ``` 4. Run the web app ```sh pnpm dev:www # Running on http://localhost:3000/ ``` ### Useful Commands From repository root: ```sh # Web app pnpm dev:www pnpm build:www # VS Code extension pnpm dev:vscode pnpm build:vscode pnpm lint:vscode pnpm lint:fix:vscode # All workspaces pnpm dev pnpm build pnpm lint ``` `pnpm build:www` is the production build command used in GitHub Actions deployment. ### Debug VS Code Extension 1. Open repository root in VS Code. 2. Press `F5`. 3. Select `Run VSCode Extension (apps/vscode)` when prompted. 4. In the Extension Development Host window, open a `.json` file and run: `JSON Crack: Enable JSON Crack visualization`. ### Docker 🐳 Docker assets are in `apps/www`. If you want to run JSON Crack locally: ```console cd apps/www # Build a Docker image with: docker compose build # Run locally with `docker-compose` docker compose up # Go to http://localhost:8888 ``` ## Configuration The supported node limit can be changed by editing `NEXT_PUBLIC_NODE_LIMIT` in `apps/www/.env`. ## License See [`LICENSE`](/LICENSE.md) for more information. ================================================ FILE: apps/vscode/.gitignore ================================================ node_modules/ build/ *.vsix ================================================ FILE: apps/vscode/.prettierignore ================================================ .github .next node_modules/ out public *-lock.json tsconfig.json build jsoncrack ================================================ FILE: apps/vscode/.prettierrc ================================================ { "trailingComma": "es5", "singleQuote": false, "semi": true, "printWidth": 100, "arrowParens": "avoid", "importOrder": [ "^(react/(.*)$)|^(react$)", "^(next/(.*)$)|^(next$)", "^@mantine/core", "^@mantine", "styled", "", "^src/(.*)$", "^[./]" ], "importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"], "plugins": ["@trivago/prettier-plugin-sort-imports"] } ================================================ FILE: apps/vscode/.vscodeignore ================================================ .vscode/** .vscode-test/** .github/** .turbo/** node_modules/** src/** ext-src/** .gitignore .prettierignore .prettierrc index.html vite.config.ts esbuild.config.mjs eslint.config.mjs **/tsconfig.json **/*.map ================================================ FILE: apps/vscode/LICENSE.md ================================================ MIT License Copyright (c) 2025 Aykut Saraç Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: apps/vscode/README.md ================================================ JSON Crack
[JSON Crack](https://jsoncrack.com?utm_source=jsoncrack-vscode&utm_medium=readme)'s Official Visual Studio Code Extension that visualizes JSON data as an interactive diagram. The extension parses the open JSON file and displays its structure as a connected graph where nodes represent objects, arrays, and values. ## How to use? 1. Install the JSON Crack extension from the [VS Code marketplace](https://marketplace.visualstudio.com/items?itemName=AykutSarac.jsoncrack-vscode). 2. Open a JSON file. 3. Click on the JSON Crack icon in the menubar at top right. image ## Privacy The extension works **fully offline**. No data is sent to any server. All JSON parsing and visualization happens locally in your editor. ## Development This extension lives in `apps/vscode` inside the [jsoncrack.com](https://github.com/AykutSarac/jsoncrack.com) monorepo. **Prerequisites:** Node.js `>=20`, pnpm `>=10` **Stack:** Vite (webview) + esbuild (extension host) + React 19 ```sh # Install dependencies from repo root pnpm install # Build the extension cd apps/vscode pnpm run build ``` ### Debugging 1. Open the **monorepo root** in VS Code. 2. Press **F5** to launch the "Run VSCode Extension" config — it builds and opens the Extension Development Host. 3. After making changes, press `Cmd+R` (macOS) / `Ctrl+R` (Windows/Linux) in the host window to reload. ### Scripts | Script | Description | |---|---| | `build` | Production build (minified, no sourcemaps) | | `build:dev` | Dev build (sourcemaps, no minification) | | `watch` | Watch extension host (`ext-src/`) for changes | | `watch:webview` | Watch webview (`src/`) for changes | | `dev` | Start Vite dev server (standalone webview in browser) | | `lint` | Run ESLint + Prettier check | | `clean` | Remove `build/` directory | ================================================ FILE: apps/vscode/esbuild.config.mjs ================================================ import * as esbuild from "esbuild"; const isWatch = process.argv.includes("--watch"); const isProduction = process.argv.includes("--production"); /** @type {esbuild.BuildOptions} */ const config = { entryPoints: ["ext-src/extension.ts"], bundle: true, outdir: "build", external: ["vscode"], format: "cjs", platform: "node", target: "node20", sourcemap: !isProduction, minify: isProduction, logLevel: "info", }; if (isWatch) { const ctx = await esbuild.context(config); await ctx.watch(); console.log("Watching for changes..."); } else { await esbuild.build(config); } ================================================ FILE: apps/vscode/eslint.config.mjs ================================================ import eslint from "@eslint/js"; import { defineConfig, globalIgnores } from "eslint/config"; import eslintConfigPrettier from "eslint-config-prettier/flat"; import eslintPluginPrettier from "eslint-plugin-prettier/recommended"; import unusedImports from "eslint-plugin-unused-imports"; import globals from "globals"; import tseslint from "typescript-eslint"; export default defineConfig([ eslint.configs.recommended, tseslint.configs.recommended, eslintConfigPrettier, eslintPluginPrettier, { languageOptions: { globals: { ...globals.browser, ...globals.node, }, parserOptions: { ecmaVersion: "latest", sourceType: "module", tsconfigRootDir: import.meta.dirname, }, }, plugins: { "unused-imports": unusedImports, }, rules: { "@typescript-eslint/consistent-type-imports": "error", "unused-imports/no-unused-imports": "error", "@typescript-eslint/no-explicit-any": "off", "prettier/prettier": "error", "space-in-parens": "error", "no-empty": "error", "no-multiple-empty-lines": "error", "no-irregular-whitespace": "error", strict: ["error", "never"], "linebreak-style": ["error", "unix"], quotes: ["error", "double", { avoidEscape: true }], semi: ["error", "always"], "prefer-const": "error", "space-before-function-paren": [ "error", { anonymous: "always", named: "never", asyncArrow: "always", }, ], }, }, globalIgnores(["build/**"]), ]); ================================================ FILE: apps/vscode/ext-src/extension.ts ================================================ import * as path from "path"; import * as vscode from "vscode"; import { createWebviewPanel } from "./webview"; function getPanelTitle(document?: vscode.TextDocument) { if (!document) return "JSON Crack"; const fileName = path.basename(document.fileName); return fileName || "JSON Crack"; } export function activate(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.commands.registerCommand("jsoncrack-vscode.start", () => createWebviewForActiveEditor(context) ), vscode.commands.registerCommand("jsoncrack-vscode.start.specific", (content?: string) => createWebviewForContent(context, content) ), vscode.commands.registerCommand("jsoncrack-vscode.start.selected", () => createWebviewForSelectedText(context) ) ); } // create webview for selected text async function createWebviewForSelectedText(context: vscode.ExtensionContext) { const editor = vscode.window.activeTextEditor; if (editor && editor.selection.isEmpty) { vscode.window.showInformationMessage("Please select some text first!"); return; } const selectedText = editor?.document.getText(editor.selection); // Create the webview panel and send the selected JSON content const panel = createWebviewPanel(context, getPanelTitle(editor?.document)); panel.webview.postMessage({ json: selectedText, }); const onReceiveMessage = panel.webview.onDidReceiveMessage(e => { if (e === "ready") { panel.webview.postMessage({ json: selectedText, }); } }); const onTextChange = vscode.workspace.onDidChangeTextDocument(changeEvent => { if (changeEvent.document === editor?.document) { panel.webview.postMessage({ json: changeEvent.document.getText(editor?.selection), }); } }); const disposer = () => { onTextChange.dispose(); onReceiveMessage.dispose(); }; panel.onDidDispose(disposer, null, context.subscriptions); } async function createWebviewForActiveEditor(context: vscode.ExtensionContext) { const editor = vscode.window.activeTextEditor; const panel = createWebviewPanel(context, getPanelTitle(editor?.document)); const onReceiveMessage = panel.webview.onDidReceiveMessage(e => { if (e === "ready") { panel.webview.postMessage({ json: editor?.document.getText(), }); } }); const onTextChange = vscode.workspace.onDidChangeTextDocument(changeEvent => { if (changeEvent.document === editor?.document) { panel.webview.postMessage({ json: changeEvent.document.getText(), }); } }); const disposer = () => { onTextChange.dispose(); onReceiveMessage.dispose(); }; panel.onDidDispose(disposer, null, context.subscriptions); } /** * Renders a readonly diagram from a string * @param context ExtensionContext * @param content JSON content as a string */ function createWebviewForContent(context?: vscode.ExtensionContext, content?: string): any { if (context && content) { const panel = createWebviewPanel( context, getPanelTitle(vscode.window.activeTextEditor?.document) ); panel.webview.postMessage({ json: content, }); } } // This method is called when your extension is deactivated export function deactivate() {} ================================================ FILE: apps/vscode/ext-src/webview.ts ================================================ import * as path from "path"; import * as vscode from "vscode"; export function createWebviewPanel(context: vscode.ExtensionContext, title = "JSON Crack") { const extPath = context.extensionPath; const webviewDir = vscode.Uri.file(path.join(extPath, "build", "webview")); const panel = vscode.window.createWebviewPanel( "liveHTMLPreviewer", title, vscode.ViewColumn.Beside, { enableScripts: true, retainContextWhenHidden: true, localResourceRoots: [webviewDir, vscode.Uri.file(path.join(extPath, "assets"))], } ); panel.iconPath = vscode.Uri.file(path.join(extPath, "assets", "jsoncrack.png")); const scriptUri = panel.webview.asWebviewUri( vscode.Uri.file(path.join(extPath, "build", "webview", "index.js")) ); const styleUri = panel.webview.asWebviewUri( vscode.Uri.file(path.join(extPath, "build", "webview", "index.css")) ); const nonce = getNonce(); const csp = [ `default-src 'self' ${panel.webview.cspSource} blob:`, `connect-src ${panel.webview.cspSource} blob:`, `script-src 'unsafe-eval' 'unsafe-inline' ${panel.webview.cspSource}`, `style-src ${panel.webview.cspSource} 'unsafe-inline'`, `worker-src ${panel.webview.cspSource} blob: data:`, ].join("; "); panel.webview.html = `
`; return panel; } function getNonce() { let text = ""; const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for (let i = 0; i < 32; i++) { text += possible.charAt(Math.floor(Math.random() * possible.length)); } return text; } ================================================ FILE: apps/vscode/index.html ================================================
================================================ FILE: apps/vscode/package.json ================================================ { "name": "vscode", "version": "3.0.0", "displayName": "JSON Crack", "description": "Seamlessly visualize your JSON data instantly into graphs.", "publisher": "AykutSarac", "license": "MIT", "author": { "email": "aykutsarac0@gmail.com", "name": "Aykut Saraç" }, "homepage": "https://jsoncrack.com", "icon": "assets/jsoncrack.png", "galleryBanner": { "color": "#202225", "theme": "dark" }, "categories": [ "Visualization" ], "keywords": [ "json", "visualizer", "jsoncrack", "data", "yaml", "livepreview" ], "activationEvents": [ "workspaceContains:**/*.{json}" ], "main": "./build/extension.js", "contributes": { "commands": [ { "command": "jsoncrack-vscode.start", "title": "Enable JSON Crack visualization", "category": "menubar", "icon": { "light": "./assets/icon-light.svg", "dark": "./assets/icon-dark.svg" } }, { "command": "jsoncrack-vscode.start.specific", "title": "Enable JSON Crack visualization for specific file", "category": "menubar" }, { "command": "jsoncrack-vscode.start.selected", "title": "Open with JSON Crack", "category": "Navigation" } ], "menus": { "editor/context": [ { "command": "jsoncrack-vscode.start.selected", "when": "editorHasSelection", "group": "navigation" } ], "commandPalette": [ { "command": "jsoncrack-vscode.start", "when": "never" } ], "editor/title": [ { "command": "jsoncrack-vscode.start", "when": "resourceExtname == .json || editorLangId == json", "group": "navigation" } ] } }, "scripts": { "vscode:prepublish": "pnpm run build", "dev": "vite", "build": "vite build && node esbuild.config.mjs --production", "build:dev": "vite build && node esbuild.config.mjs", "watch": "node esbuild.config.mjs --watch", "watch:webview": "vite build --watch", "lint": "eslint src ext-src && prettier --check src ext-src", "lint:fix": "eslint --fix src ext-src && prettier --write src ext-src", "clean": "rm -rf build" }, "devDependencies": { "@eslint/js": "^10.0.1", "@mantine/code-highlight": "^8.3.18", "@mantine/core": "^8.3.18", "@mantine/hooks": "^8.3.18", "@trivago/prettier-plugin-sort-imports": "^6.0.2", "@types/node": "^22.19.15", "@types/react": "19.2.11", "@types/react-dom": "19.2.3", "@types/vscode": "^1.110.0", "@typescript-eslint/eslint-plugin": "^8.57.1", "@typescript-eslint/parser": "^8.57.1", "@vitejs/plugin-react": "^4.7.0", "esbuild": "^0.25.12", "eslint": "^10.0.3", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-unused-imports": "^4.4.1", "globals": "^17.4.0", "prettier": "^3.8.1", "typescript": "^5.9.3", "typescript-eslint": "^8.57.1", "vite": "^6.4.1" }, "dependencies": { "jsoncrack-react": "workspace:*", "react": "19.2.4", "react-dom": "19.2.4", "shiki": "^4.0.2" }, "repository": { "type": "git", "url": "https://github.com/AykutSarac/jsoncrack.com" }, "bugs": { "url": "https://github.com/AykutSarac/jsoncrack.com/issues" }, "engines": { "vscode": "^1.86.0" }, "packageManager": "pnpm@10.20.0" } ================================================ FILE: apps/vscode/src/App.tsx ================================================ import { useCallback, useEffect, useState } from "react"; import { Anchor, Box, MantineProvider, Text } from "@mantine/core"; import { CodeHighlightAdapterProvider, createShikiAdapter } from "@mantine/code-highlight"; import type { NodeData } from "jsoncrack-react"; import { JSONCrack } from "jsoncrack-react"; import { NodeModal } from "./components/NodeModal"; async function loadShiki() { const { createHighlighter } = await import("shiki"); return createHighlighter({ langs: ["json"], themes: [] }); } const shikiAdapter = createShikiAdapter(loadShiki); function getTheme() { const theme = document.body.getAttribute("data-vscode-theme-kind"); if (theme?.includes("light")) return "light" as const; return "dark"; } const App: React.FC = () => { const [json, setJson] = useState("{}"); const [selectedNode, setSelectedNode] = useState(null); const theme = getTheme(); useEffect(() => { const vscode = window?.acquireVsCodeApi?.(); vscode?.postMessage("ready"); const onMessage = (event: MessageEvent<{ json?: string }>) => { const jsonData = event.data?.json; if (typeof jsonData === "string") { setJson(jsonData); } }; window.addEventListener("message", onMessage); return () => { window.removeEventListener("message", onMessage); }; }, []); const handleNodeClick = useCallback((node: NodeData) => { setSelectedNode(node); }, []); const closeNodeModal = useCallback(() => { setSelectedNode(null); }, []); return ( {selectedNode && ( )} Powered by JSON Crack ); }; export default App; declare global { interface Window { acquireVsCodeApi?: () => any; } } ================================================ FILE: apps/vscode/src/components/NodeModal.tsx ================================================ import type { ModalProps } from "@mantine/core"; import { Modal, Stack, Text, ScrollArea } from "@mantine/core"; import { CodeHighlight } from "@mantine/code-highlight"; import type { NodeData } from "jsoncrack-react"; interface NodeModalProps extends ModalProps { nodeData: NodeData | null; } const normalizeNodeData = (nodeRows: NodeData["text"]) => { if (!nodeRows || nodeRows.length === 0) return "{}"; if (nodeRows.length === 1 && !nodeRows[0].key) return `${nodeRows[0].value}`; const obj: Record = {}; nodeRows.forEach(row => { if (row.type !== "array" && row.type !== "object" && row.key) { obj[row.key] = row.value; } }); return JSON.stringify(obj, null, 2); }; const jsonPathToString = (path?: NodeData["path"]) => { if (!path || path.length === 0) return "$"; const segments = path.map(seg => (typeof seg === "number" ? seg : `"${seg}"`)); return `$[${segments.join("][")}]`; }; export const NodeModal = ({ opened, onClose, nodeData }: NodeModalProps) => { const nodeContent = normalizeNodeData(nodeData?.text ?? []); const jsonPath = jsonPathToString(nodeData?.path); return ( Content JSON Path ); }; ================================================ FILE: apps/vscode/src/env.d.ts ================================================ /// ================================================ FILE: apps/vscode/src/global.css ================================================ html, body, #root { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; } *, *::before, *::after { box-sizing: border-box; } a { color: inherit; text-decoration: none; } ================================================ FILE: apps/vscode/src/index.tsx ================================================ import "@mantine/core/styles.css"; import "@mantine/code-highlight/styles.css"; import "jsoncrack-react/style.css"; import { createRoot } from "react-dom/client"; import App from "./App"; import "./global.css"; const container = document.getElementById("root") as HTMLElement; const root = createRoot(container); root.render(); ================================================ FILE: apps/vscode/tsconfig.json ================================================ { "compilerOptions": { "target": "ES2020", "lib": ["dom", "dom.iterable", "esnext"], "module": "esnext", "moduleResolution": "bundler", "jsx": "react-jsx", "strict": true, "skipLibCheck": true, "esModuleInterop": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "noImplicitAny": false }, "include": ["src", "ext-src"], "exclude": ["node_modules"] } ================================================ FILE: apps/vscode/vite.config.ts ================================================ import react from "@vitejs/plugin-react"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); export default defineConfig({ plugins: [react()], build: { outDir: "build/webview", emptyOutDir: true, rollupOptions: { input: "src/index.tsx", output: { entryFileNames: "index.js", assetFileNames: "index[extname]", inlineDynamicImports: true, }, }, }, resolve: { alias: { react: path.resolve(__dirname, "node_modules/react"), "react-dom": path.resolve(__dirname, "node_modules/react-dom"), }, }, }); ================================================ FILE: apps/www/.dockerignore ================================================ Dockerfile .dockerignore node_modules npm-debug.log README.md .next .git ================================================ FILE: apps/www/.gitignore ================================================ # App dependencies node_modules/ # Next.js .next/ out/ # Turborepo task cache .turbo/ # Test/build artifacts coverage/ build/ *.tsbuildinfo # Local environment files .env.local .env.development.local .env.test.local .env.production.local # Deployment .vercel # Workspace migration artifact (root lockfile is authoritative) pnpm-lock.yaml # PWA workers public/workbox-*.js public/sw.js public/fallback-*.js ================================================ FILE: apps/www/.prettierignore ================================================ .github .next node_modules/ out public *-lock.json tsconfig.json ================================================ FILE: apps/www/.prettierrc ================================================ { "trailingComma": "es5", "singleQuote": false, "semi": true, "printWidth": 100, "arrowParens": "avoid", "importOrder": [ "^(react/(.*)$)|^(react$)", "^(next/(.*)$)|^(next$)", "^@mantine/core", "^@mantine", "styled", "", "^src/(.*)$", "^[./]" ], "importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"], "plugins": ["@trivago/prettier-plugin-sort-imports"] } ================================================ FILE: apps/www/Dockerfile ================================================ FROM node:24.10.0-alpine AS base RUN npm install -g pnpm@10.10.0 # Stage 1: Install dependencies FROM base AS deps WORKDIR /app COPY package.json pnpm-lock.yaml ./ RUN corepack enable pnpm && pnpm install --frozen-lockfile # Stage 2: Build the application FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . RUN corepack enable pnpm && pnpm run build # Stage 3: Production image FROM nginxinc/nginx-unprivileged:stable AS production WORKDIR /app COPY --from=builder /app/out /app COPY ./nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 8080 ================================================ FILE: apps/www/LICENSE.md ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2025 Aykut Saraç Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: apps/www/docker-compose.yml ================================================ services: jsoncrack: image: jsoncrack container_name: jsoncrack build: context: . dockerfile: Dockerfile ports: - "8888:8080" environment: - NODE_ENV=production ================================================ FILE: apps/www/eslint.config.mjs ================================================ import eslint from "@eslint/js"; import nextPlugin from "@next/eslint-plugin-next"; import eslintConfigPrettier from "eslint-config-prettier/flat"; import eslintPluginPrettier from "eslint-plugin-prettier/recommended"; import unusedImports from "eslint-plugin-unused-imports"; import { defineConfig, globalIgnores } from "eslint/config"; import tseslint from "typescript-eslint"; export default defineConfig([ globalIgnores(["src/enums", "**/next.config.js", "src/lib/utils/json2go.js"]), eslint.configs.recommended, tseslint.configs.recommended, { plugins: { "@next/next": nextPlugin, }, rules: { ...nextPlugin.configs.recommended.rules, ...nextPlugin.configs["core-web-vitals"].rules, "@next/next/no-img-element": "off", }, }, eslintConfigPrettier, eslintPluginPrettier, { languageOptions: { parserOptions: { tsconfigRootDir: import.meta.dirname, }, }, plugins: { "unused-imports": unusedImports, }, rules: { "@typescript-eslint/consistent-type-imports": "error", "unused-imports/no-unused-imports": "error", "@typescript-eslint/no-explicit-any": "off", "prettier/prettier": "error", "space-in-parens": "error", "no-empty": "error", "no-multiple-empty-lines": "error", "no-irregular-whitespace": "error", strict: ["error", "never"], "linebreak-style": ["error", "unix"], quotes: ["error", "double", { avoidEscape: true }], semi: ["error", "always"], "prefer-const": "error", "space-before-function-paren": [ "error", { anonymous: "always", named: "never", asyncArrow: "always", }, ], }, }, ]); ================================================ FILE: apps/www/next-env.d.ts ================================================ /// /// import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. ================================================ FILE: apps/www/next-sitemap.config.js ================================================ /** @type {import('next-sitemap').IConfig} */ module.exports = { siteUrl: "https://jsoncrack.com", exclude: ["/widget"], autoLastmod: false, changefreq: "never", }; ================================================ FILE: apps/www/next.config.js ================================================ const withBundleAnalyzer = require("@next/bundle-analyzer")({ enabled: process.env.ANALYZE === "true", }); /** * @type {import('next').NextConfig} */ const config = { output: "export", transpilePackages: ["jsoncrack"], reactStrictMode: false, productionBrowserSourceMaps: true, compiler: { styledComponents: true, }, turbopack: { resolveAlias: { fs: { browser: "./shims/empty.ts", }, }, }, webpack: (config, { isServer }) => { config.resolve.fallback = { fs: false }; config.output.webassemblyModuleFilename = "static/wasm/[modulehash].wasm"; config.experiments = { asyncWebAssembly: true, layers: true }; if (!isServer) { config.output.environment = { ...config.output.environment, asyncFunction: true }; } return config; }, }; const configExport = () => { if (process.env.ANALYZE === "true") return withBundleAnalyzer(config); return config; }; module.exports = configExport(); ================================================ FILE: apps/www/nginx.conf ================================================ server { listen 8080; root /app; include /etc/nginx/mime.types; location /editor { try_files $uri /editor.html; } location /widget { try_files $uri /widget.html; } location /docs { try_files $uri /docs.html; } } ================================================ FILE: apps/www/package.json ================================================ { "name": "www", "private": true, "version": "0.0.0", "license": "Apache-2.0", "scripts": { "dev": "next dev --webpack", "build": "next build --webpack", "postbuild": "next-sitemap --config next-sitemap.config.js", "start": "next start", "lint": "tsc --project tsconfig.json && eslint src && prettier --check src", "lint:fix": "eslint --fix src && prettier --write src", "analyze": "ANALYZE=true npm run build" }, "dependencies": { "@mantine/code-highlight": "^8.3.18", "@mantine/core": "^8.3.18", "@mantine/dropzone": "^8.3.18", "@mantine/hooks": "^8.3.18", "@monaco-editor/react": "^4.7.0", "allotment": "^1.20.5", "fast-xml-parser": "5.5.6", "gofmt.js": "0.0.2", "html-to-image": "1.11.13", "jq-web": "0.6.2", "js-yaml": "4.1.1", "json-2-csv": "5.5.10", "json-schema-faker": "0.6.0", "json_typegen_wasm": "0.7.0", "jsonc-parser": "3.3.1", "jsoncrack-react": "workspace:*", "jsonpath-plus": "10.4.0", "lodash.debounce": "^4.0.8", "next": "16.1.7", "next-seo": "^7.2.0", "next-sitemap": "^4.2.3", "nextjs-google-analytics": "^2.3.7", "react": "19.2.4", "react-dom": "19.2.4", "react-hot-toast": "^2.6.0", "react-icons": "^5.6.0", "react-json-tree": "^0.20.0", "react-linkify-it": "^2.0.0", "react-zoomable-ui": "^0.11.0", "reaflow": "5.4.1", "shiki": "^4.0.2", "styled-components": "^6.3.11", "use-long-press": "^3.3.0", "zustand": "^5.0.12" }, "devDependencies": { "@eslint/js": "^10.0.1", "@next/bundle-analyzer": "16.1.7", "@next/eslint-plugin-next": "16.1.7", "@trivago/prettier-plugin-sort-imports": "^6.0.2", "@types/js-yaml": "^4.0.9", "@types/node": "^25.5.0", "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "eslint": "^10.0.3", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-unused-imports": "^4.4.1", "prettier": "^3.8.1", "ts-node": "^10.9.2", "typescript": "5.9.3", "typescript-eslint": "^8.57.1" }, "packageManager": "pnpm@10.20.0" } ================================================ FILE: apps/www/public/.nojekyll ================================================ ================================================ FILE: apps/www/public/CNAME ================================================ jsoncrack.com ================================================ FILE: apps/www/public/manifest.json ================================================ { "name": "JSON Crack", "short_name": "JSON Crack", "description": "JSON Crack Editor is a tool for visualizing into graphs, analyzing, editing, formatting, querying, transforming and validating JSON, CSV, YAML, XML, and more.", "theme_color": "#36393e", "background_color": "#36393e", "display": "standalone", "orientation": "landscape", "scope": "/editor", "start_url": "/editor", "icons": [ { "src": "assets/192.png", "sizes": "192x192", "type": "image/png" }, { "src": "assets/512.png", "sizes": "512x512", "type": "image/png" } ] } ================================================ FILE: apps/www/public/robots.txt ================================================ User-agent: * Allow: / Sitemap: https://jsoncrack.com/sitemap.xml ================================================ FILE: apps/www/public/sitemap-0.xml ================================================ https://jsoncrack.comnever0.7 https://jsoncrack.com/converter/csv-to-jsonnever0.7 https://jsoncrack.com/converter/csv-to-xmlnever0.7 https://jsoncrack.com/converter/csv-to-yamlnever0.7 https://jsoncrack.com/converter/json-to-csvnever0.7 https://jsoncrack.com/converter/json-to-xmlnever0.7 https://jsoncrack.com/converter/json-to-yamlnever0.7 https://jsoncrack.com/converter/xml-to-csvnever0.7 https://jsoncrack.com/converter/xml-to-jsonnever0.7 https://jsoncrack.com/converter/xml-to-yamlnever0.7 https://jsoncrack.com/converter/yaml-to-csvnever0.7 https://jsoncrack.com/converter/yaml-to-jsonnever0.7 https://jsoncrack.com/converter/yaml-to-xmlnever0.7 https://jsoncrack.com/docsnever0.7 https://jsoncrack.com/editornever0.7 https://jsoncrack.com/legal/privacynever0.7 https://jsoncrack.com/legal/termsnever0.7 https://jsoncrack.com/tools/json-schemanever0.7 https://jsoncrack.com/type/csv-to-gonever0.7 https://jsoncrack.com/type/csv-to-kotlinnever0.7 https://jsoncrack.com/type/csv-to-rustnever0.7 https://jsoncrack.com/type/csv-to-typescriptnever0.7 https://jsoncrack.com/type/json-to-gonever0.7 https://jsoncrack.com/type/json-to-kotlinnever0.7 https://jsoncrack.com/type/json-to-rustnever0.7 https://jsoncrack.com/type/json-to-typescriptnever0.7 https://jsoncrack.com/type/xml-to-gonever0.7 https://jsoncrack.com/type/xml-to-kotlinnever0.7 https://jsoncrack.com/type/xml-to-rustnever0.7 https://jsoncrack.com/type/xml-to-typescriptnever0.7 https://jsoncrack.com/type/yaml-to-gonever0.7 https://jsoncrack.com/type/yaml-to-kotlinnever0.7 https://jsoncrack.com/type/yaml-to-rustnever0.7 https://jsoncrack.com/type/yaml-to-typescriptnever0.7 ================================================ FILE: apps/www/public/sitemap.xml ================================================ https://jsoncrack.com/sitemap-0.xml ================================================ FILE: apps/www/shims/empty.ts ================================================ export {}; ================================================ FILE: apps/www/src/constants/globalStyle.ts ================================================ import { createGlobalStyle } from "styled-components"; const GlobalStyle = createGlobalStyle` html, body { background: #ffffff; overscroll-behavior: none; -webkit-font-smoothing: subpixel-antialiased !important; } *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; scroll-behavior: smooth !important; -webkit-tap-highlight-color: transparent; -webkit-font-smoothing: never; } .hide { display: none; } svg { vertical-align: text-top; } a { color: unset; text-decoration: none; } button { border: none; outline: none; background: transparent; width: fit-content; margin: 0; padding: 0; cursor: pointer; } `; export default GlobalStyle; ================================================ FILE: apps/www/src/constants/graph.ts ================================================ export const NODE_DIMENSIONS = { ROW_HEIGHT: 30, // Regular row height PARENT_HEIGHT: 36, // Height for parent nodes } as const; export const SUPPORTED_LIMIT = +(process.env.NEXT_PUBLIC_NODE_LIMIT as string); ================================================ FILE: apps/www/src/constants/seo.ts ================================================ import type { DefaultSeoProps } from "next-seo/pages"; export const SEO: DefaultSeoProps = { title: "JSON Crack | Online JSON Viewer - Transform your data into interactive graphs", description: "JSON Crack Editor is a tool for visualizing into graphs, analyzing, editing, formatting, querying, transforming and validating JSON, CSV, YAML, XML, and more.", themeColor: "#36393E", openGraph: { type: "website", images: [ { url: "https://jsoncrack.com/assets/jsoncrack.png", width: 1200, height: 627, }, ], }, twitter: { handle: "@jsoncrack", cardType: "summary_large_image", }, additionalLinkTags: [ { rel: "manifest", href: "/manifest.json", }, { rel: "icon", href: "/favicon.ico", sizes: "48x48", }, ], }; ================================================ FILE: apps/www/src/constants/theme.ts ================================================ const fixedColors = { CRIMSON: "#DC143C", BLURPLE: "#5865F2", PURPLE: "#9036AF", FULL_WHITE: "#FFFFFF", BLACK: "#202225", BLACK_DARK: "#2C2F33", BLACK_LIGHT: "#2F3136", BLACK_PRIMARY: "#36393f", DARK_SALMON: "#E9967A", DANGER: "hsl(359,calc(var(--saturation-factor, 1)*66.7%),54.1%)", LIGHTGREEN: "#90EE90", SEAGREEN: "#11883B", ORANGE: "#FAA81A", SILVER: "#B9BBBE", PRIMARY: "#4D4D4D", TEXT_DANGER: "#db662e", }; const nodeColors = { dark: { NODE_COLORS: { TEXT: "#DCE5E7", NODE_KEY: "#59b8ff", NODE_VALUE: "#DCE5E7", INTEGER: "#e8c479", NULL: "#939598", BOOL: { FALSE: "#F85C50", TRUE: "#00DC7D", }, PARENT_ARR: "#FC9A40", PARENT_OBJ: "#59b8ff", CHILD_COUNT: "white", DIVIDER: "#383838", }, }, light: { NODE_COLORS: { TEXT: "#000", NODE_KEY: "#761CEA", NODE_VALUE: "#535353", INTEGER: "#FD0079", NULL: "#afafaf", BOOL: { FALSE: "#FF0000", TRUE: "#748700", }, PARENT_ARR: "#FF6B00", PARENT_OBJ: "#761CEA", CHILD_COUNT: "#535353", DIVIDER: "#e6e6e6", }, }, }; export const darkTheme = { ...fixedColors, ...nodeColors.dark, BLACK_SECONDARY: "#23272A", SILVER_DARK: "#4D4D4D", NODE_KEY: "#FAA81A", OBJECT_KEY: "#59b8ff", SIDEBAR_ICONS: "#8B8E90", INTERACTIVE_NORMAL: "#b9bbbe", INTERACTIVE_HOVER: "#dcddde", INTERACTIVE_ACTIVE: "#fff", BACKGROUND_NODE: "#2B2C3E", BACKGROUND_TERTIARY: "#202225", BACKGROUND_SECONDARY: "#2f3136", TOOLBAR_BG: "#262626", BACKGROUND_PRIMARY: "#36393f", BACKGROUND_MODIFIER_ACCENT: "rgba(79,84,92,0.48)", MODAL_BACKGROUND: "#36393E", TEXT_NORMAL: "#dcddde", TEXT_POSITIVE: "hsl(139,calc(var(--saturation-factor, 1)*51.6%),52.2%)", GRID_BG_COLOR: "#141414", GRID_COLOR_PRIMARY: "#1c1b1b", GRID_COLOR_SECONDARY: "#191919", }; export const lightTheme = { ...fixedColors, ...nodeColors.light, BLACK_SECONDARY: "#F2F2F2", SILVER_DARK: "#CCCCCC", NODE_KEY: "#DC3790", OBJECT_KEY: "#0260E8", SIDEBAR_ICONS: "#6D6E70", INTERACTIVE_NORMAL: "#4f5660", INTERACTIVE_HOVER: "#2e3338", INTERACTIVE_ACTIVE: "#060607", BACKGROUND_NODE: "#F6F8FA", BACKGROUND_TERTIARY: "#e3e5e8", BACKGROUND_SECONDARY: "#f2f3f5", TOOLBAR_BG: "#ECECEC", BACKGROUND_PRIMARY: "#FFFFFF", BACKGROUND_MODIFIER_ACCENT: "rgba(106,116,128,0.24)", MODAL_BACKGROUND: "#FFFFFF", TEXT_NORMAL: "#2e3338", TEXT_POSITIVE: "#008736", GRID_BG_COLOR: "#f7f7f7", GRID_COLOR_PRIMARY: "#ebe8e8", GRID_COLOR_SECONDARY: "#f2eeee", }; const themeDs = { ...lightTheme, ...darkTheme, }; export default themeDs; ================================================ FILE: apps/www/src/data/example.json ================================================ { "fruits": [ { "name": "Apple", "color": "#FF0000", "details": { "type": "Pome", "season": "Fall" }, "nutrients": { "calories": 52, "fiber": "2.4g", "vitaminC": "4.6mg" } }, { "name": "Banana", "color": "#FFFF00", "details": { "type": "Berry", "season": "Year-round" }, "nutrients": { "calories": 89, "fiber": "2.6g", "potassium": "358mg" } }, { "name": "Orange", "color": "#FFA500", "details": { "type": "Citrus", "season": "Winter" }, "nutrients": { "calories": 47, "fiber": "2.4g", "vitaminC": "53.2mg" } } ] } ================================================ FILE: apps/www/src/data/faq.json ================================================ [ { "title": "What is JSON Crack and what does it do?", "content": "JSON Crack is an online JSON viewer tool designed to visualize and analyze various data formats, including JSON, YAML, CSV, XML and more. It transforms complex data into intuitive graphs and tree views, making it ideal for developers, data analysts, and anyone working with structured data." }, { "title": "How is it different than traditional JSON viewers?", "content": "While traditional JSON Viewers and JSON formatters only allow you to work with raw data on text editors, JSON Crack offers a unique visual representation of your data, making it easier to understand and analyze complex data structures. It provides a tree view and graph view to help you visualize your data in different ways." }, { "title": "Is JSON Crack free?", "content": "Yes, JSON Crack is a free-forever open source online tool. For advanced features you may use ToDiagram.com" }, { "title": "Is my data secure?", "content": "Yes. When you paste or import your data into the editor, it's processed only on your browser to create the visualization without going into our servers." }, { "title": "Can I convert JSON to other formats using JSON Crack?", "content": "Yes, JSON Crack offers robust data conversion capabilities. You can easily convert JSON to YAML, XML to JSON, CSV to JSON and other popular formats." }, { "title": "What kind of data formats are supported?", "content": "A wide range of data formats are supported including JSON, YAML, XML, and CSV." }, { "title": "What size of data can I visualize?", "content": "It supports approximately 300 KB. It might vary depending on the complexity of the data and your hardware." }, { "title": "Can I export the generated graphs?", "content": "Yes, you can export the generated graphs as PNG, JPEG, or SVG files." }, { "title": "How to use VS Code extension?", "content": "You can use the VS Code extension to visualize JSON data directly in your editor. Install the extension from the VS Code marketplace and follow the instructions at extension's page." }, { "title": "I've previously subscribed to the premium plan, where did it go?", "content": "We have moved the premium features to ToDiagram.com. You can use the same credentials to access the premium features or manage your subscription." } ] ================================================ FILE: apps/www/src/data/privacy.json ================================================ { "Introduction": [ "Welcome to JSON Crack.", "JSON Crack (“us”, “we”, or “our”) operates https://jsoncrack.com (hereinafter referred to as “Service”).", "Our Privacy Policy governs your visit to https://jsoncrack.com, explaining how we collect, safeguard, and disclose information resulting from your use of our Service.", "By using the Service, you agree to the collection and use of information in accordance with this policy. Unless otherwise defined in this Privacy Policy, the terms used have the same meanings as in our Terms and Conditions.", "Our Terms and Conditions (“Terms”) govern all use of our Service and together with the Privacy Policy constitute your agreement with us (“agreement”)." ], "Definitions": [ "SERVICE means the https://jsoncrack.com website.", "PERSONAL DATA means data about a living individual who can be identified from those data.", "USAGE DATA is data collected automatically, generated by the use of Service or from Service infrastructure itself (e.g., the duration of a page visit).", "COOKIES are small files stored on your device (computer or mobile device)." ], "Information Collection and Use": [ "We collect limited information for the purpose of improving our Service. We do not collect or store any Personal Data beyond what is necessary for analytics and security." ], "Data Collection": [ "Usage Data", "• We may collect information that your browser sends whenever you visit our Service (“Usage Data”).", "• This may include your computer's IP address, browser type, browser version, pages visited, time and date of your visit, time spent on pages, and other diagnostic data.", "Cookies", "• We use cookies and similar tracking technologies to monitor activity on our Service.", "• Cookies are files with a small amount of data that may include an anonymous unique identifier." ], "Use of Data": [ "JSON Crack uses collected data to:", "• Provide and maintain the Service;", "• Improve and analyze Service performance." ], "Security of Data": [ "The data you paste into the editor for transformation into visualizations is not stored or processed on our servers, ensuring that your information remains private.", "Usage Data may be shared with third-party analytics services as outlined below." ], "Analytics": [ "We may use Google Analytics to monitor Service usage.", "• Google Analytics collects non-personally identifying information such as browser type, referring pages, and time spent on the site.", "• We do not collect IP addresses through Google Analytics.", "• Learn more about Google's privacy practices: https://support.google.com/analytics/answer/4597324?hl=en" ], "Changes to This Privacy Policy": [ "We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page.", "You are advised to review this Privacy Policy periodically for any changes. Changes are effective when posted on this page." ], "Contact Us": [ "If you have any questions about this Privacy Policy, please contact us at contact@todiagram.com." ] } ================================================ FILE: apps/www/src/data/terms.json ================================================ { "Introduction": [ "Subject to these Terms of Service (“Terms”, “Terms of Service”), jsoncrack.com ('JSON Crack', 'we', 'us' and/or 'our') provides access to JSON Crack's application as a service (collectively, the 'Services'). By using or accessing the Services, you acknowledge that you have read, understand, and agree to be bound by this Agreement.", "These Terms of Service govern your use of our web pages located at https://jsoncrack.com.", "Our Privacy Policy also governs your use of our Service and explains how we collect, safeguard and disclose information that results from your use of our web pages. Please read it here https://jsoncrack.com/legal/privacy.", "Your agreement with us includes these Terms and our Privacy Policy (“Agreements”). You acknowledge that you have read and understood Agreements, and agree to be bound of them.", "If you do not agree with (or cannot comply with) Agreements, then you may not use the Service, but please let us know by emailing at contact@todiagram.com so we can try to find a solution. These Terms apply to all visitors, users and others who wish to access or use Service.", "Thank you for being responsible." ], "Prohibited Uses": [ "You may use Service only for lawful purposes and in accordance with Terms. You agree not to use Service:", "• In any way that violates any applicable national or international law or regulation.", "• For the purpose of exploiting, harming, or attempting to exploit or harm minors in any way by exposing them to inappropriate content or otherwise.", "• To transmit, or procure the sending of, any advertising or promotional material, including any “junk mail”, “chain letter,” “spam,” or any other similar solicitation.", "• In any way that infringes upon the rights of others, or in any way is illegal, threatening, fraudulent, or harmful, or in connection with any unlawful, illegal, fraudulent, or harmful purpose or activity.", "• To engage in any other conduct that restricts or inhibits anyone's use or enjoyment of Service, or which, as determined by us, may harm or offend Company or users of Service or expose them to liability.", "Additionally, you agree not to:", "• Use Service in any manner that could disable, overburden, damage, or impair Service or interfere with any other party's use of Service, including their ability to engage in real time activities through Service.", "• Use any robot, spider, or other automatic device, process, or means to access Service for any purpose, including monitoring or copying any of the material on Service.", "• Use any manual process to monitor or copy any of the material on Service or for any other unauthorized purpose without our prior written consent.", "• Use any device, software, or routine that interferes with the proper working of Service.", "• Introduce any viruses, trojan horses, worms, logic bombs, or other material which is malicious or technologically harmful.", "• Attempt to gain unauthorized access to, interfere with, damage, or disrupt any parts of Service, the server on which Service is stored, or any server, computer, or database connected to Service.", "• Attack Service via a denial-of-service attack or a distributed denial-of-service attack.", "• Take any action that may damage or falsify Company rating.", "• Otherwise attempt to interfere with the proper working of Service." ], "Analytics": [ "We may use third-party Service Providers to monitor and analyze the use of our Service.", "Google Analytics is a web analytics service offered by Google that tracks and reports website traffic. Google uses the data collected to track and monitor the use of our Service. This data is shared with other Google services. Google may use the collected data to contextualise and personalise the ads of its own advertising network.", "For more information on the privacy practices of Google, please visit the Google Privacy Terms web page: https://policies.google.com/privacy?hl=en", "We also encourage you to review the Google's policy for safeguarding your data: https://support.google.com/analytics/answer/6004245" ], "Data Security": [ "As we do not store any user data on our servers, we are not responsible for any data security risks that may occur when users store or process data locally on their devices." ], "Intellectual Property": [ "Service and its original content (excluding Content provided by users), features and functionality are and will remain the exclusive property of JSON Crack and its licensors. Service is protected by copyright and other laws of foreign countries. Our trademarks and trade dress may not be used in connection with any product or service without the prior written consent of JSON Crack." ], "Disclaimer of Warranty": [ "THESE SERVICES ARE PROVIDED BY COMPANY ON AN “AS IS” AND “AS AVAILABLE” BASIS. COMPANY MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, AS TO THE OPERATION OF THEIR SERVICES, OR THE INFORMATION, CONTENT OR MATERIALS INCLUDED THEREIN. YOU EXPRESSLY AGREE THAT YOUR USE OF THESE SERVICES, THEIR CONTENT, AND ANY SERVICES OR ITEMS OBTAINED FROM US IS AT YOUR SOLE RISK.", "NEITHER COMPANY NOR ANY PERSON ASSOCIATED WITH COMPANY MAKES ANY WARRANTY OR REPRESENTATION WITH RESPECT TO THE COMPLETENESS, SECURITY, RELIABILITY, QUALITY, ACCURACY, OR AVAILABILITY OF THE SERVICES. WITHOUT LIMITING THE FOREGOING, NEITHER COMPANY NOR ANYONE ASSOCIATED WITH COMPANY REPRESENTS OR WARRANTS THAT THE SERVICES, THEIR CONTENT, OR ANY SERVICES OR ITEMS OBTAINED THROUGH THE SERVICES WILL BE ACCURATE, RELIABLE, ERROR-FREE, OR UNINTERRUPTED, THAT DEFECTS WILL BE CORRECTED, THAT THE SERVICES OR THE SERVER THAT MAKES IT AVAILABLE ARE FREE OF VIRUSES OR OTHER HARMFUL COMPONENTS OR THAT THE SERVICES OR ANY SERVICES OR ITEMS OBTAINED THROUGH THE SERVICES WILL OTHERWISE MEET YOUR NEEDS OR EXPECTATIONS.", "COMPANY HEREBY DISCLAIMS ALL WARRANTIES OF ANY KIND, WHETHER EXPRESS OR IMPLIED, STATUTORY, OR OTHERWISE, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, NON-INFRINGEMENT, AND FITNESS FOR PARTICULAR PURPOSE.", "THE FOREGOING DOES NOT AFFECT ANY WARRANTIES WHICH CANNOT BE EXCLUDED OR LIMITED UNDER APPLICABLE LAW." ], "Limitation of Liability": [ "EXCEPT AS PROHIBITED BY LAW, YOU WILL HOLD US AND OUR OFFICERS, DIRECTORS, EMPLOYEES, AND AGENTS HARMLESS FOR ANY INDIRECT, PUNITIVE, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGE, HOWEVER IT ARISES (INCLUDING ATTORNEYS' FEES AND ALL RELATED COSTS AND EXPENSES OF LITIGATION AND ARBITRATION, OR AT TRIAL OR ON APPEAL, IF ANY, WHETHER OR NOT LITIGATION OR ARBITRATION IS INSTITUTED), WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE, OR OTHER TORTIOUS ACTION, OR ARISING OUT OF OR IN CONNECTION WITH THIS AGREEMENT, INCLUDING WITHOUT LIMITATION ANY CLAIM FOR PERSONAL INJURY OR PROPERTY DAMAGE, ARISING FROM THIS AGREEMENT AND ANY VIOLATION BY YOU OF ANY FEDERAL, STATE, OR LOCAL LAWS, STATUTES, RULES, OR REGULATIONS, EVEN IF COMPANY HAS BEEN PREVIOUSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. EXCEPT AS PROHIBITED BY LAW, IF THERE IS LIABILITY FOUND ON THE PART OF COMPANY, IT WILL BE LIMITED TO THE AMOUNT PAID FOR THE PRODUCTS AND/OR SERVICES, AND UNDER NO CIRCUMSTANCES WILL THERE BE CONSEQUENTIAL OR PUNITIVE DAMAGES. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF PUNITIVE, INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THE PRIOR LIMITATION OR EXCLUSION MAY NOT APPLY TO YOU." ], "Termination": [ "We may terminate or suspend your account and bar access to Service immediately, without prior notice or liability, under our sole discretion, for any reason whatsoever and without limitation, including but not limited to a breach of Terms.", "If you wish to terminate your account, you may simply discontinue using Service.", "All provisions of Terms which by their nature should survive termination shall survive termination, including, without limitation, ownership provisions, warranty disclaimers, indemnity and limitations of liability." ], "Changes To Service": [ "We reserve the right to withdraw or amend our Service, and any service or material we provide via Service, in our sole discretion without notice. We will not be liable if for any reason all or any part of Service is unavailable at any time or for any period. From time to time, we may restrict access to some parts of Service, or the entire Service, to users, including registered users." ], "Amendments To Terms": [ "We may amend Terms at any time by posting the amended terms on this site. It is your responsibility to review these Terms periodically.", "Your continued use of the Platform following the posting of revised Terms means that you accept and agree to the changes. You are expected to check this page frequently so you are aware of any changes, as they are binding on you.", "By continuing to access or use our Service after any revisions become effective, you agree to be bound by the revised terms. If you do not agree to the new terms, you are no longer authorized to use Service." ], "Acknowledgement": [ "BY USING SERVICE OR OTHER SERVICES PROVIDED BY US, YOU ACKNOWLEDGE THAT YOU HAVE READ THESE TERMS OF SERVICE AND AGREE TO BE BOUND BY THEM." ], "Contact Us": [ "If you have any questions about these terms of service, please contact us:", "By email: contact@todiagram.com." ] } ================================================ FILE: apps/www/src/enums/file.enum.ts ================================================ export enum FileFormat { "JSON" = "json", "YAML" = "yaml", "XML" = "xml", "CSV" = "csv", } export const formats = [ { value: FileFormat.JSON, label: "JSON" }, { value: FileFormat.YAML, label: "YAML" }, { value: FileFormat.XML, label: "XML" }, { value: FileFormat.CSV, label: "CSV" }, ]; export enum TypeLanguage { TypeScript = "typescript", TypeScript_Combined = "typescript/typealias", Go = "go", JSON_SCHEMA = "json_schema", Kotlin = "kotlin", Rust = "rust", } export const typeOptions = [ { label: "TypeScript", value: TypeLanguage.TypeScript, lang: "typescript", }, { label: "TypeScript (merged)", value: TypeLanguage.TypeScript_Combined, lang: "typescript", }, { label: "Go", value: TypeLanguage.Go, lang: "go", }, { label: "JSON Schema", value: TypeLanguage.JSON_SCHEMA, lang: "json", }, { label: "Kotlin", value: TypeLanguage.Kotlin, lang: "kotlin", }, { label: "Rust", value: TypeLanguage.Rust, lang: "rust", }, ]; ================================================ FILE: apps/www/src/enums/viewMode.enum.ts ================================================ export enum ViewMode { Graph = "graph", Tree = "tree", } ================================================ FILE: apps/www/src/features/Banner.tsx ================================================ import React, { useEffect, useState } from "react"; import { Anchor, Flex, Button, ActionIcon } from "@mantine/core"; import { useSessionStorage } from "@mantine/hooks"; import { MdClose } from "react-icons/md"; export const BANNER_HEIGHT = process.env.NEXT_PUBLIC_DISABLE_EXTERNAL_MODE === "true" ? "0px" : "40px"; const BANNER_LIST = [ "Save and store your diagrams with ToDiagram", "Explore the ToDiagram from the creators of JSON Crack", "Generate AI diagrams with single prompt", "Try ToDiagram for free, no sign-up required", "Edit data directly inside diagrams", "Explore larger datasets (up to 50 MB) easily", ]; export const Banner = () => { const ROTATION_INTERVAL = 6000; // ms between label changes const FADE_DURATION = 500; // ms for fade transition const [index, setIndex] = useState(0); const [visible, setVisible] = useState(true); const [dismissed, setDismissed] = useSessionStorage({ key: "jsoncrack_banner_dismissed", defaultValue: false, }); useEffect(() => { if (dismissed) return; let fadeTimeout: ReturnType | undefined; const intervalId = setInterval(() => { setVisible(false); fadeTimeout = setTimeout(() => { setIndex(i => (i + 1) % BANNER_LIST.length); setVisible(true); }, FADE_DURATION); }, ROTATION_INTERVAL); return () => { clearInterval(intervalId); if (fadeTimeout) clearTimeout(fadeTimeout); }; }, [dismissed]); const handleDismiss = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); setDismissed(true); }; if (dismissed) return null; return ( {BANNER_LIST[index]}{" "} ); }; ================================================ FILE: apps/www/src/features/editor/BottomBar.tsx ================================================ import React from "react"; import { Flex, Menu, Popover, Text } from "@mantine/core"; import styled from "styled-components"; import { event as gaEvent } from "nextjs-google-analytics"; import { BiSolidDockLeft } from "react-icons/bi"; import { IoMdCheckmark } from "react-icons/io"; import { MdArrowUpward } from "react-icons/md"; import { VscCheck, VscError, VscRunAll, VscSync, VscSyncIgnored } from "react-icons/vsc"; import { formats } from "../../enums/file.enum"; import useConfig from "../../store/useConfig"; import useFile from "../../store/useFile"; import useGraph from "./views/GraphView/stores/useGraph"; const StyledBottomBar = styled.div` position: relative; display: flex; align-items: center; justify-content: space-between; border-top: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT}; background: ${({ theme }) => theme.TOOLBAR_BG}; max-height: 27px; height: 27px; z-index: 35; padding-right: 6px; @media screen and (max-width: 320px) { display: none; } `; const StyledLeft = styled.div` display: flex; align-items: center; justify-content: left; gap: 4px; padding-left: 8px; @media screen and (max-width: 480px) { display: none; } `; const StyledRight = styled.div` display: flex; align-items: center; justify-content: right; gap: 4px; `; const StyledBottomBarItem = styled.button<{ $bg?: string }>` display: flex; align-items: center; gap: 4px; width: fit-content; margin: 0; height: 28px; padding: 4px; font-size: 12px; font-weight: 400; color: ${({ theme }) => theme.INTERACTIVE_NORMAL}; background: ${({ $bg }) => $bg}; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; &:hover:not(&:disabled) { background-image: linear-gradient(rgba(0, 0, 0, 0.1) 0 0); color: ${({ theme }) => theme.INTERACTIVE_HOVER}; } &:disabled { opacity: 0.6; cursor: default; } `; export const BottomBar = () => { const data = useFile(state => state.fileData); const toggleLiveTransform = useConfig(state => state.toggleLiveTransform); const liveTransformEnabled = useConfig(state => state.liveTransformEnabled); const error = useFile(state => state.error); const setContents = useFile(state => state.setContents); const toggleFullscreen = useGraph(state => state.toggleFullscreen); const fullscreen = useGraph(state => state.fullscreen); const setFormat = useFile(state => state.setFormat); const currentFormat = useFile(state => state.format); const toggleEditor = () => { toggleFullscreen(!fullscreen); gaEvent("toggle_fullscreen"); }; React.useEffect(() => { if (data?.name) window.document.title = `${data.name} | JSON Crack`; }, [data]); return ( {error ? ( Invalid {error} ) : ( Valid )} { toggleLiveTransform(!liveTransformEnabled); gaEvent("toggle_live_transform"); }} > {liveTransformEnabled ? : } Live Transform {!liveTransformEnabled && ( setContents({})} disabled={!!error}> Click to Transform )} {currentFormat?.toUpperCase()} {formats.map(format => ( setFormat(format.value)} rightSection={currentFormat === format.value && } > {format.label} ))} ); }; ================================================ FILE: apps/www/src/features/editor/ExternalMode.tsx ================================================ import React from "react"; import { Accordion, Anchor, Code, Flex, FocusTrap, Group, Modal, Text } from "@mantine/core"; const ExternalMode = () => { const [isExternal, setExternal] = React.useState(false); React.useEffect(() => { if (process.env.NEXT_PUBLIC_DISABLE_EXTERNAL_MODE === "false") { if (typeof window !== "undefined") { if (window.location.pathname.includes("widget")) return setExternal(false); if (window.location.host !== "jsoncrack.com") return setExternal(true); return setExternal(false); } } }, []); if (!isExternal) return null; return ( setExternal(false)} centered size="lg" > How can I change the file size limit? The main reason for the file size limit is to prevent performance issues, not to push you to upgrade. You can increase the limit by setting{" "} NEXT_PUBLIC_NODE_LIMIT in your .env file.

If you'd like to work with even larger files and unlock additional features, you can upgrade to the{" "} Pro {" "} version.
How can I stop this dialog from appearing? You can disable this dialog by setting NEXT_PUBLIC_DISABLE_EXTERNAL_MODE{" "} to true in your .env.development file.

If you want to re-enable it, simply remove or set the value to false.
What are the license terms? Read the full license terms on{" "} GitHub . How do I report a bug or request a feature? You can report bugs or request features by opening an issue on our{" "} GitHub Issues page .

Please provide as much detail as possible to help us address your feedback quickly.
How do I contribute to the project? We welcome contributions! Visit our{" "} GitHub repository {" "} and read the{" "} contributing guide {" "} to get started. What is the difference between JSON Crack and ToDiagram? JSON Crack is a free and open-source tool for visualizing JSON data. ToDiagram is the professional version that offers advanced features, higher limits, and the ability to edit data directly from diagrams. You can learn more or upgrade at{" "} todiagram.com .
GitHub ToDiagram Aykut Saraç (@aykutsarach)
); }; export default ExternalMode; ================================================ FILE: apps/www/src/features/editor/FullscreenDropzone.tsx ================================================ import React from "react"; import { Group, Text } from "@mantine/core"; import { Dropzone } from "@mantine/dropzone"; import toast from "react-hot-toast"; import { VscCircleSlash, VscFiles } from "react-icons/vsc"; import { FileFormat } from "../../enums/file.enum"; import useFile from "../../store/useFile"; export const FullscreenDropzone = () => { const setContents = useFile(state => state.setContents); return ( toast.error(`Unable to load file ${files[0].file.name}`)} onDrop={async e => { try { const fileContent = await e[0].text(); let fileExtension = e[0].name.split(".").pop() as FileFormat | undefined; if (!fileExtension) fileExtension = FileFormat.JSON; setContents({ contents: fileContent, format: fileExtension, hasChanges: false }); } catch (err) { toast.error("An error occurred while reading the file."); console.error(err); } }} > Upload to JSON Crack (Max file size: 300 KB) Invalid file Allowed formats are JSON, YAML, CSV, XML ); }; ================================================ FILE: apps/www/src/features/editor/LiveEditor.tsx ================================================ import React from "react"; import { useSessionStorage } from "@mantine/hooks"; import styled from "styled-components"; import { ViewMode } from "../../enums/viewMode.enum"; import { GraphView } from "./views/GraphView"; import { TreeView } from "./views/TreeView"; const StyledLiveEditor = styled.div` position: relative; height: 100%; background: ${({ theme }) => theme.GRID_BG_COLOR}; overflow: auto; & > ul { margin-top: 0 !important; padding: 12px !important; font-family: monospace; font-size: 14px; font-weight: 500; } .tab-group { position: absolute; top: 10px; left: 10px; z-index: 2; } `; const View = () => { const [viewMode] = useSessionStorage({ key: "viewMode", defaultValue: ViewMode.Graph, }); if (viewMode === ViewMode.Graph) return ; if (viewMode === ViewMode.Tree) return ; return null; }; const LiveEditor = () => { return ( e.preventDefault()}> ); }; export default LiveEditor; ================================================ FILE: apps/www/src/features/editor/TextEditor.tsx ================================================ import React, { useCallback } from "react"; import { LoadingOverlay } from "@mantine/core"; import styled from "styled-components"; import Editor, { type EditorProps, loader, type OnMount, useMonaco } from "@monaco-editor/react"; import useConfig from "../../store/useConfig"; import useFile from "../../store/useFile"; loader.config({ paths: { vs: "https://unpkg.com/monaco-editor@0.55.1/min/vs", }, }); const editorOptions: EditorProps["options"] = { tabSize: 2, formatOnType: true, minimap: { enabled: false }, stickyScroll: { enabled: false }, scrollBeyondLastLine: false, placeholder: "Start typing...", }; const TextEditor = () => { const monaco = useMonaco(); const contents = useFile(state => state.contents); const setContents = useFile(state => state.setContents); const setError = useFile(state => state.setError); const jsonSchema = useFile(state => state.jsonSchema); const getHasChanges = useFile(state => state.getHasChanges); const theme = useConfig(state => (state.darkmodeEnabled ? "vs-dark" : "light")); const fileType = useFile(state => state.format); const jsonDefaults = (monaco?.languages as any)?.json?.jsonDefaults as | { setDiagnosticsOptions: (options: unknown) => void } | undefined; React.useEffect(() => { if (!jsonDefaults) return; jsonDefaults.setDiagnosticsOptions({ validate: true, allowComments: true, enableSchemaRequest: true, ...(jsonSchema && { schemas: [ { uri: "http://myserver/foo-schema.json", fileMatch: ["*"], schema: jsonSchema, }, ], }), }); }, [jsonDefaults, jsonSchema]); React.useEffect(() => { const beforeunload = (e: BeforeUnloadEvent) => { if (getHasChanges()) { const confirmationMessage = "Unsaved changes, if you leave before saving your changes will be lost"; (e || window.event).returnValue = confirmationMessage; //Gecko + IE return confirmationMessage; } }; window.addEventListener("beforeunload", beforeunload); return () => { window.removeEventListener("beforeunload", beforeunload); }; }, [getHasChanges]); const handleMount: OnMount = useCallback(editor => { editor.onDidPaste(() => { editor.getAction("editor.action.formatDocument")?.run(); }); }, []); return ( setError(errors[0]?.message || "")} onChange={contents => setContents({ contents, skipUpdate: true })} loading={} /> ); }; export default TextEditor; const StyledEditorWrapper = styled.div` display: flex; flex-direction: column; height: 100%; user-select: none; `; const StyledWrapper = styled.div` display: grid; height: 100%; grid-template-columns: 100%; grid-template-rows: minmax(0, 1fr); `; ================================================ FILE: apps/www/src/features/editor/Toolbar/FileMenu.tsx ================================================ import React from "react"; import { Flex, Menu } from "@mantine/core"; import { event as gaEvent } from "nextjs-google-analytics"; import { CgChevronDown } from "react-icons/cg"; import useFile from "../../../store/useFile"; import { useModal } from "../../../store/useModal"; import { StyledToolElement } from "./styles"; export const FileMenu = () => { const setVisible = useModal(state => state.setVisible); const getContents = useFile(state => state.getContents); const getFormat = useFile(state => state.getFormat); const handleSave = () => { const a = document.createElement("a"); const file = new Blob([getContents()], { type: "text/plain" }); a.href = window.URL.createObjectURL(file); a.download = `jsoncrack.${getFormat()}`; a.click(); gaEvent("save_file", { label: getFormat() }); }; return ( File setVisible("ImportModal", true)}>Import Export ); }; ================================================ FILE: apps/www/src/features/editor/Toolbar/SearchInput.tsx ================================================ import React from "react"; import { Flex, Text, TextInput } from "@mantine/core"; import { getHotkeyHandler } from "@mantine/hooks"; import { useOs } from "@mantine/hooks"; import { AiOutlineSearch } from "react-icons/ai"; import { useFocusNode } from "../../../hooks/useFocusNode"; export const SearchInput = () => { const [searchValue, setValue, skip, nodeCount, currentNode] = useFocusNode(); const os = useOs(); const coreKey = os === "macos" ? "⌘" : "Ctrl"; return ( setValue(e.currentTarget.value)} placeholder={`Search Node (${coreKey} + F)`} autoComplete="off" autoCorrect="off" onKeyDown={getHotkeyHandler([["Enter", skip]])} leftSection={} rightSection={ searchValue && ( {searchValue && `${nodeCount}/${nodeCount > 0 ? currentNode + 1 : "0"}`} ) } style={{ borderBottom: "1px solid gray" }} /> ); }; ================================================ FILE: apps/www/src/features/editor/Toolbar/ThemeToggle.tsx ================================================ import { FaMoon, FaSun } from "react-icons/fa6"; import useConfig from "../../../store/useConfig"; import { StyledToolElement } from "./styles"; export const ThemeToggle = () => { const darkmodeEnabled = useConfig(state => state.darkmodeEnabled); const toggleDarkMode = useConfig(state => state.toggleDarkMode); return ( toggleDarkMode(!darkmodeEnabled)}> {!darkmodeEnabled ? : } ); }; ================================================ FILE: apps/www/src/features/editor/Toolbar/ToolsMenu.tsx ================================================ import React from "react"; import { Menu, Flex } from "@mantine/core"; import { event as gaEvent } from "nextjs-google-analytics"; import { CgChevronDown } from "react-icons/cg"; import { MdFilterListAlt } from "react-icons/md"; import { VscSearchFuzzy, VscJson, VscGroupByRefType } from "react-icons/vsc"; import { useModal } from "../../../store/useModal"; import { StyledToolElement } from "./styles"; export const ToolsMenu = () => { const setVisible = useModal(state => state.setVisible); return ( gaEvent("show_tools_menu")}> Tools } onClick={() => { setVisible("JQModal", true); gaEvent("open_jq_modal"); }} > JSON Query (jq) } onClick={() => { setVisible("JPathModal", true); gaEvent("open_json_path_modal"); }} > JSON Path } onClick={() => { setVisible("SchemaModal", true); gaEvent("open_schema_modal"); }} > JSON Schema } onClick={() => { setVisible("TypeModal", true); gaEvent("open_type_modal"); }} > Generate Type ); }; ================================================ FILE: apps/www/src/features/editor/Toolbar/ViewMenu.tsx ================================================ import { Menu, Flex, SegmentedControl } from "@mantine/core"; import { useSessionStorage } from "@mantine/hooks"; import { event as gaEvent } from "nextjs-google-analytics"; import { CgChevronDown } from "react-icons/cg"; import { ViewMode } from "../../../enums/viewMode.enum"; import { StyledToolElement } from "./styles"; export const ViewMenu = () => { const [viewMode, setViewMode] = useSessionStorage({ key: "viewMode", defaultValue: ViewMode.Graph, }); return ( gaEvent("show_view_menu")}> View { setViewMode(e as ViewMode); gaEvent("change_view_mode", { label: e }); }} data={[ { value: ViewMode.Graph, label: "Graph" }, { value: ViewMode.Tree, label: "Tree" }, ]} fullWidth orientation="vertical" /> ); }; ================================================ FILE: apps/www/src/features/editor/Toolbar/index.tsx ================================================ import React from "react"; import Link from "next/link"; import { Flex, Group } from "@mantine/core"; import styled from "styled-components"; import toast from "react-hot-toast"; import { AiOutlineFullscreen } from "react-icons/ai"; import { FaGithub } from "react-icons/fa6"; import { JSONCrackLogo } from "../../../layout/JSONCrackBrandLogo"; import { FileMenu } from "./FileMenu"; import { ThemeToggle } from "./ThemeToggle"; import { ToolsMenu } from "./ToolsMenu"; import { ViewMenu } from "./ViewMenu"; import { StyledToolElement } from "./styles"; const StyledTools = styled.div` position: relative; display: flex; width: 100%; align-items: center; gap: 4px; justify-content: space-between; height: 45px; padding: 6px 12px; background: ${({ theme }) => theme.TOOLBAR_BG}; color: ${({ theme }) => theme.SILVER}; z-index: 36; border-bottom: 1px solid ${({ theme }) => theme.SILVER_DARK}; @media only screen and (max-width: 320px) { display: none; } `; function fullscreenBrowser() { if (!document.fullscreenElement) { document.documentElement.requestFullscreen().catch(() => { toast.error("Unable to enter fullscreen mode."); }); } else if (document.exitFullscreen) { document.exitFullscreen(); } } export const Toolbar = () => { return ( ); }; ================================================ FILE: apps/www/src/features/editor/Toolbar/styles.ts ================================================ import styled from "styled-components"; export const StyledToolElement = styled.button<{ $hide?: boolean; $highlight?: boolean }>` display: ${({ $hide }) => ($hide ? "none" : "flex")}; align-items: center; gap: 4px; place-content: center; font-size: 14px; background: ${({ $highlight }) => $highlight ? "linear-gradient(rgba(0, 0, 0, 0.1) 0 0)" : "none"}; color: ${({ theme }) => theme.INTERACTIVE_NORMAL}; padding: 6px; border-radius: 3px; white-space: nowrap; &:hover { background-image: linear-gradient(rgba(0, 0, 0, 0.1) 0 0); } &:hover { color: ${({ theme }) => theme.INTERACTIVE_HOVER}; opacity: 1; box-shadow: none; } `; ================================================ FILE: apps/www/src/features/editor/views/GraphView/CustomEdge/index.tsx ================================================ import React from "react"; import { useComputedColorScheme } from "@mantine/core"; import type { EdgeProps } from "reaflow"; import { Edge } from "reaflow"; import useGraph from "../stores/useGraph"; const CustomEdgeWrapper = (props: EdgeProps) => { const colorScheme = useComputedColorScheme(); const viewPort = useGraph(state => state.viewPort); const [hovered, setHovered] = React.useState(false); const handeClick = () => { const targetNodeId = (props.properties as { to?: string } | undefined)?.to; const targetNodeDom = document.querySelector( `[data-id$="node-${targetNodeId}"]` ) as HTMLElement; if (targetNodeDom && targetNodeDom.parentElement) { viewPort?.camera.centerFitElementIntoView(targetNodeDom.parentElement, { elementExtraMarginForZoom: 150, }); } }; return ( setHovered(true)} onLeave={() => setHovered(false)} style={{ stroke: colorScheme === "dark" ? "#444444" : "#BCBEC0", ...(hovered && { stroke: "#3B82F6" }), strokeWidth: 1.5, }} {...props} /> ); }; export const CustomEdge = React.memo(CustomEdgeWrapper); ================================================ FILE: apps/www/src/features/editor/views/GraphView/CustomNode/ObjectNode.tsx ================================================ import React from "react"; import type { NodeData } from "jsoncrack-react"; import type { CustomNodeProps } from "."; import { NODE_DIMENSIONS } from "../../../../../constants/graph"; import { TextRenderer } from "./TextRenderer"; import * as Styled from "./styles"; type RowProps = { row: NodeData["text"][number]; x: number; y: number; index: number; }; const Row = ({ row, x, y, index }: RowProps) => { const rowPosition = index * NODE_DIMENSIONS.ROW_HEIGHT; const getRowText = () => { if (row.type === "object") return `{${row.childrenCount ?? 0} keys}`; if (row.type === "array") return `[${row.childrenCount ?? 0} items]`; return row.value; }; return ( {row.key}: {getRowText()} ); }; const Node = ({ node, x, y }: CustomNodeProps) => ( {node.text.map((row, index) => ( ))} ); function propsAreEqual(prev: CustomNodeProps, next: CustomNodeProps) { return ( JSON.stringify(prev.node.text) === JSON.stringify(next.node.text) && prev.node.width === next.node.width ); } export const ObjectNode = React.memo(Node, propsAreEqual); ================================================ FILE: apps/www/src/features/editor/views/GraphView/CustomNode/TextNode.tsx ================================================ import React from "react"; import styled from "styled-components"; import type { CustomNodeProps } from "."; import { TextRenderer } from "./TextRenderer"; import * as Styled from "./styles"; const StyledTextNodeWrapper = styled.span<{ $isParent: boolean }>` display: flex; justify-content: ${({ $isParent }) => ($isParent ? "center" : "flex-start")}; align-items: center; height: 100%; width: 100%; overflow: hidden; padding: 0 10px; `; const Node = ({ node, x, y }: CustomNodeProps) => { const { text, width, height } = node; const value = text[0].value; return ( {value} ); }; function propsAreEqual(prev: CustomNodeProps, next: CustomNodeProps) { return prev.node.text === next.node.text && prev.node.width === next.node.width; } export const TextNode = React.memo(Node, propsAreEqual); ================================================ FILE: apps/www/src/features/editor/views/GraphView/CustomNode/TextRenderer.tsx ================================================ import React from "react"; import { ColorSwatch } from "@mantine/core"; import styled from "styled-components"; const StyledRow = styled.span` display: inline-flex; align-items: center; overflow: hidden; gap: 4px; vertical-align: middle; `; const isURL = (word: string) => { const urlPattern = /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/gm; return word?.match(urlPattern); }; const Linkify = (text: string) => { const addMarkup = (word: string) => { return isURL(word) ? `${word}` : word; }; const words = text.split(" "); const formatedWords = words.map(w => addMarkup(w)); const html = formatedWords.join(" "); return ; }; export const TextRenderer = ({ children }: React.PropsWithChildren) => { if (typeof children === "string" && isURL(children)) return Linkify(children); if (typeof children === "string" && isColorFormat(children)) { return ( {children} ); } return <>{`${children}`}; }; function isColorFormat(colorString: string) { const hexCodeRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/; const rgbRegex = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/; const rgbaRegex = /^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(0|1|0\.\d+)\s*\)$/; return ( hexCodeRegex.test(colorString) || rgbRegex.test(colorString) || rgbaRegex.test(colorString) ); } ================================================ FILE: apps/www/src/features/editor/views/GraphView/CustomNode/index.tsx ================================================ import React from "react"; import { useComputedColorScheme } from "@mantine/core"; import type { NodeData } from "jsoncrack-react"; import type { NodeProps } from "reaflow"; import { Node } from "reaflow"; import { useModal } from "../../../../../store/useModal"; import useGraph from "../stores/useGraph"; import { ObjectNode } from "./ObjectNode"; import { TextNode } from "./TextNode"; export interface CustomNodeProps { node: NodeData; x: number; y: number; hasCollapse?: boolean; } const CustomNodeWrapper = (nodeProps: NodeProps) => { const setSelectedNode = useGraph(state => state.setSelectedNode); const setVisible = useModal(state => state.setVisible); const colorScheme = useComputedColorScheme(); const handleNodeClick = React.useCallback( (_: React.MouseEvent, data: NodeData) => { if (setSelectedNode) setSelectedNode(data); setVisible("NodeModal", true); }, [setSelectedNode, setVisible] ); return ( { ev.currentTarget.style.stroke = "#3B82F6"; }} onLeave={ev => { ev.currentTarget.style.stroke = colorScheme === "dark" ? "#424242" : "#BCBEC0"; }} style={{ fill: colorScheme === "dark" ? "#292929" : "#ffffff", stroke: colorScheme === "dark" ? "#424242" : "#BCBEC0", strokeWidth: 1, }} > {({ node, x, y }) => { const hasKey = nodeProps.properties.text[0].key; if (!hasKey) return ; return ; }} ); }; export const CustomNode = React.memo(CustomNodeWrapper); ================================================ FILE: apps/www/src/features/editor/views/GraphView/CustomNode/styles.tsx ================================================ import type { DefaultTheme } from "styled-components"; import styled from "styled-components"; import { LinkItUrl } from "react-linkify-it"; import { NODE_DIMENSIONS } from "../../../../../constants/graph"; type TextColorFn = { theme: DefaultTheme; $type?: string; $value?: string | number | null | boolean; }; function getTextColor({ $value, $type, theme }: TextColorFn) { if ($value === null) return theme.NODE_COLORS.NULL; if ($type === "object") return theme.NODE_COLORS.NODE_KEY; if ($type === "number") return theme.NODE_COLORS.INTEGER; if ($value === true) return theme.NODE_COLORS.BOOL.TRUE; if ($value === false) return theme.NODE_COLORS.BOOL.FALSE; return theme.NODE_COLORS.NODE_VALUE; } export const StyledLinkItUrl = styled(LinkItUrl)` text-decoration: underline; pointer-events: all; `; export const StyledForeignObject = styled.foreignObject<{ $isObject?: boolean }>` text-align: ${({ $isObject }) => !$isObject && "center"}; color: ${({ theme }) => theme.NODE_COLORS.TEXT}; font-family: monospace; font-size: 12px; font-weight: 500; overflow: hidden; pointer-events: none; &.searched { background: rgba(27, 255, 0, 0.1); border: 1px solid ${({ theme }) => theme.TEXT_POSITIVE}; border-radius: 2px; box-sizing: border-box; } .highlight { background: rgba(255, 214, 0, 0.15); } .renderVisible { display: flex; justify-content: center; align-items: center; font-size: 12px; width: 100%; height: 100%; overflow: hidden; cursor: pointer; } `; export const StyledKey = styled.span<{ $type: TextColorFn["$type"]; $value?: TextColorFn["$value"]; }>` display: inline; align-items: center; justify-content: center; flex: 1; min-width: 0; height: auto; line-height: inherit; padding: 0; // Remove padding color: ${({ theme, $type, $value = "" }) => getTextColor({ $value, $type, theme })}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; `; export const StyledRow = styled.span<{ $value: TextColorFn["$value"] }>` padding: 3px 10px; height: ${NODE_DIMENSIONS.ROW_HEIGHT}px; line-height: 24px; color: ${({ theme, $value }) => getTextColor({ $value, theme, $type: typeof $value })}; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border-bottom: 1px solid ${({ theme }) => theme.NODE_COLORS.DIVIDER}; box-sizing: border-box; &:last-of-type { border-bottom: none; } .searched & { border-bottom: 1px solid ${({ theme }) => theme.TEXT_POSITIVE}; } `; export const StyledChildrenCount = styled.span` color: ${({ theme }) => theme.NODE_COLORS.CHILD_COUNT}; padding: 10px; margin-left: -15px; `; ================================================ FILE: apps/www/src/features/editor/views/GraphView/NotSupported.tsx ================================================ import React from "react"; import { Anchor, Button, Image, Overlay, Stack, Text } from "@mantine/core"; import styled, { keyframes } from "styled-components"; import useConfig from "../../../../store/useConfig"; const shineEffect = keyframes` 0% { transform: translateX(-120%) rotate(25deg); opacity: 0.5; } 5% { opacity: 0.5; transform: translateX(-80%) rotate(25deg); } 70% { transform: translateX(80%) rotate(25deg); opacity: 0.5; } 80% { transform: translateX(120%) rotate(25deg); opacity: 0; } 100% { transform: translateX(120%) rotate(25deg); opacity: 0; } `; const ShiningButton = styled.div` position: relative; overflow: hidden; display: inline-block; border-radius: 0.5rem; z-index: 10; &::before { content: ""; position: absolute; top: -50%; left: -50%; width: 200%; height: 200%; background: linear-gradient( to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0) 35%, rgba(255, 255, 255, 0.5) 50%, rgba(255, 255, 255, 0) 65%, rgba(255, 255, 255, 0) 100% ); transform: translateX(-120%) rotate(25deg); z-index: 20; pointer-events: none; animation: ${shineEffect} 4s ease-out infinite; transition: transform 0.2s ease-out; } `; export const NotSupported = () => { const darkmodeEnabled = useConfig(state => state.darkmodeEnabled); return ( Unsupported Time to upgrade! This diagram is too large and not supported at JSON Crack.
Try{" "} ToDiagram {" "} for larger diagrams and more features.
); }; ================================================ FILE: apps/www/src/features/editor/views/GraphView/OptionsMenu.tsx ================================================ import React from "react"; import { ActionIcon, Flex, Menu, Text } from "@mantine/core"; import { useHotkeys } from "@mantine/hooks"; import styled from "styled-components"; import type { LayoutDirection } from "jsoncrack-react"; import { event as gaEvent } from "nextjs-google-analytics"; import { BsCheck2 } from "react-icons/bs"; import { LuImageDown, LuMenu } from "react-icons/lu"; import { TiFlowMerge } from "react-icons/ti"; import useConfig from "../../../../store/useConfig"; import { useModal } from "../../../../store/useModal"; import useGraph from "./stores/useGraph"; const StyledFlowIcon = styled(TiFlowMerge)<{ rotate: number }>` transform: rotate(${({ rotate }) => `${rotate}deg`}); `; const getNextDirection = (direction: LayoutDirection) => { if (direction === "RIGHT") return "DOWN"; if (direction === "DOWN") return "LEFT"; if (direction === "LEFT") return "UP"; return "RIGHT"; }; const rotateLayout = (direction: LayoutDirection) => { if (direction === "LEFT") return 90; if (direction === "UP") return 180; if (direction === "RIGHT") return 270; return 360; }; export const OptionsMenu = () => { const toggleGestures = useConfig(state => state.toggleGestures); const toggleRulers = useConfig(state => state.toggleRulers); const gesturesEnabled = useConfig(state => state.gesturesEnabled); const rulersEnabled = useConfig(state => state.rulersEnabled); const setDirection = useGraph(state => state.setDirection); const direction = useGraph(state => state.direction); const setVisible = useModal(state => state.setVisible); const [coreKey, setCoreKey] = React.useState("CTRL"); const toggleDirection = () => { const nextDirection = getNextDirection(direction || "RIGHT"); if (setDirection) setDirection(nextDirection); }; useHotkeys( [ ["mod+shift+d", toggleDirection], [ "mod+f", () => { const input = document.querySelector("#search-node") as HTMLInputElement; input.focus(); }, ], ], [] ); React.useEffect(() => { if (typeof window !== "undefined") { setCoreKey(navigator.userAgent.indexOf("Mac OS X") ? "⌘" : "CTRL"); } }, []); return ( } onClick={() => setVisible("DownloadModal", true)} > Export {coreKey} + S { toggleDirection(); gaEvent("rotate_layout", { label: direction }); }} leftSection={ } rightSection={ {coreKey} Shift D } closeMenuOnClick={false} > Rotate Layout View Options } onClick={() => { toggleRulers(!rulersEnabled); gaEvent("toggle_rulers", { label: rulersEnabled ? "on" : "off" }); }} closeMenuOnClick={false} > Rulers } onClick={() => { toggleGestures(!gesturesEnabled); gaEvent("toggle_gestures", { label: gesturesEnabled ? "on" : "off" }); }} > Zoom on Scroll ); }; ================================================ FILE: apps/www/src/features/editor/views/GraphView/SecureInfo.tsx ================================================ import React from "react"; import { ThemeIcon, Tooltip } from "@mantine/core"; import { LuShieldCheck } from "react-icons/lu"; export const SecureInfo = () => { return ( ); }; ================================================ FILE: apps/www/src/features/editor/views/GraphView/ZoomControl.tsx ================================================ import React from "react"; import { ActionIcon, Flex, Tooltip, Text } from "@mantine/core"; import { useHotkeys } from "@mantine/hooks"; import { event as gaEvent } from "nextjs-google-analytics"; import { LuFocus, LuMaximize, LuMinus, LuPlus } from "react-icons/lu"; import { SearchInput } from "../../Toolbar/SearchInput"; import useGraph from "./stores/useGraph"; export const ZoomControl = () => { const zoomIn = useGraph(state => state.zoomIn); const zoomOut = useGraph(state => state.zoomOut); const centerView = useGraph(state => state.centerView); const focusFirstNode = useGraph(state => state.focusFirstNode); useHotkeys( [ ["mod+[plus]", zoomIn, { usePhysicalKeys: true }], ["mod+[minus]", zoomOut, { usePhysicalKeys: true }], ["shift+Digit1", focusFirstNode, { usePhysicalKeys: true }], ["shift+Digit2", centerView, { usePhysicalKeys: true }], ], [] ); return ( Center first item ⇧ 1 } withArrow > { focusFirstNode(); gaEvent("focus_first_node"); }} > Fit to center ⇧ 2 } withArrow > { centerView(); gaEvent("center_view"); }} > { zoomOut(); gaEvent("zoom_out"); }} > { zoomIn(); gaEvent("zoom_in"); }} > ); }; ================================================ FILE: apps/www/src/features/editor/views/GraphView/index.tsx ================================================ import React from "react"; import { Box } from "@mantine/core"; import styled from "styled-components"; import { JSONCrack } from "jsoncrack-react"; import type { NodeData } from "jsoncrack-react"; import { useLongPress } from "use-long-press"; import { SUPPORTED_LIMIT } from "../../../../constants/graph"; import useConfig from "../../../../store/useConfig"; import useJson from "../../../../store/useJson"; import { useModal } from "../../../../store/useModal"; import { NotSupported } from "./NotSupported"; import { OptionsMenu } from "./OptionsMenu"; import { SecureInfo } from "./SecureInfo"; import { ZoomControl } from "./ZoomControl"; import useGraph from "./stores/useGraph"; const StyledEditorWrapper = styled.div<{ $widget: boolean }>` width: 100%; height: 100%; .jsoncrack-space, .jsoncrack-space:active { cursor: url("/assets/cursor.svg"), auto; } `; interface GraphProps { isWidget?: boolean; } export const GraphView = ({ isWidget = false }: GraphProps) => { const setViewPort = useGraph(state => state.setViewPort); const direction = useGraph(state => state.direction); const setSelectedNode = useGraph(state => state.setSelectedNode); const gesturesEnabled = useConfig(state => state.gesturesEnabled); const rulersEnabled = useConfig(state => state.rulersEnabled); const darkmodeEnabled = useConfig(state => state.darkmodeEnabled); const json = useJson(state => state.json); const setVisible = useModal(state => state.setVisible); const callback = React.useCallback(() => { const canvas = document.querySelector(".jsoncrack-canvas") as HTMLDivElement | null; canvas?.classList.add("dragging"); }, []); const bindLongPress = useLongPress(callback, { threshold: 150, onFinish: () => { const canvas = document.querySelector(".jsoncrack-canvas") as HTMLDivElement | null; canvas?.classList.remove("dragging"); }, }); const blurOnClick = React.useCallback(() => { if ("activeElement" in document) { (document.activeElement as HTMLElement | null)?.blur(); } }, []); const handleNodeClick = React.useCallback( (node: NodeData) => { setSelectedNode(node); setVisible("NodeModal", true); }, [setSelectedNode, setVisible] ); const maxVisibleNodes = Number.isFinite(SUPPORTED_LIMIT) ? SUPPORTED_LIMIT : 1500; return ( {!isWidget && } {!isWidget && } event.preventDefault()} onClick={blurOnClick} {...bindLongPress()} > } /> ); }; ================================================ FILE: apps/www/src/features/editor/views/GraphView/lib/jsonParser.ts ================================================ /** * Copyright (c) JSON Crack * This source code is licensed under the Apache 2.0 license found in the * LICENSE file in the root directory of this source tree. */ import { parseTree, getNodePath, type Node } from "jsonc-parser"; import type { EdgeData, NodeData, NodeRow } from "jsoncrack-react"; import { calculateNodeSize } from "./utils/calculateNodeSize"; export type Graph = { nodes: NodeData[]; edges: EdgeData[]; }; export const parser = (json: string): Graph => { const jsonTree = parseTree(json); if (!jsonTree) return { nodes: [], edges: [] }; const nodes: NodeData[] = []; const edges: EdgeData[] = []; let nodeId = 1; let edgeId = 1; function traverse(node: Node, parentId?: string) { const id = String(nodeId++); const text: NodeRow[] = []; // If parentId is provided, create an edge from parentId to the current node id if (parentId !== undefined && node.parent?.type === "array") { edges.push({ id: String(edgeId++), from: parentId, to: id, text: "", }); } const isArray = node.type === "array"; const isRootArray = !node.parent || node.parent.type === "array"; if (isArray && isRootArray) { const { width, height } = calculateNodeSize(`[${node.children?.length ?? "0"} items]`); nodes.push({ id, text: [ { key: null, value: `[${node.children?.length ?? 0} items]`, type: "array", childrenCount: node.children?.length, }, ], width, height, path: [], }); node.children?.forEach(child => { traverse(child, id); }); return id; } node.children?.forEach(child => { if (!child.children || !child.children[1]) return traverse(child, id); const key = child.children[0].value ?? null; const valueNode = child.children[1]; const type = valueNode.type; if (type === "array") { const targetIds: string[] = []; valueNode.children?.forEach(arrayChild => { const arrayChildId = traverse(arrayChild, undefined); if (arrayChildId) targetIds.push(arrayChildId); }); text.push({ key, value: valueNode.value, type, to: targetIds.length > 0 ? targetIds : undefined, childrenCount: valueNode.children?.length, }); targetIds.forEach(targetId => { edges.push({ id: String(edgeId++), from: id, to: targetId, text: key ?? null, }); }); } else if (type === "object") { const objectNodeId = traverse(valueNode, id); text.push({ key, value: valueNode.value, type, childrenCount: Object.keys(valueNode.children ?? {}).length, ...(objectNodeId && { to: [objectNodeId] }), }); if (objectNodeId) { edges.push({ id: String(edgeId++), from: id, to: objectNodeId, text: key ?? null, }); } } else { text.push({ key, value: valueNode.value, type, }); } }); // to handle case where empty object inside array [{}] if (node.parent?.type === "array" && node.type === "object" && node.children?.length === 0) { text.push({ key: null, value: "{0 keys}", type: "object", childrenCount: 0, }); } const appendParentKey = () => { const getParentKey = (targetNode: any) => { const path = getNodePath(targetNode); return path?.pop()?.toString(); }; if (!node.parent) { return { parentKey: getParentKey(node), parentType: node.type }; } if (node.parent.type === "array") { return { parentKey: getParentKey(node.parent), parentType: "array" }; } if (node.parent.type === "property") { return { parentKey: getParentKey(node), parentType: "object" }; } return { parentKey: getParentKey(node), parentType: node.parent.type.replace("property", "object"), }; }; // its for singular text like string, number, boolean, null if (text.length === 0) { if (typeof node.value === "undefined") return; const { width, height } = calculateNodeSize(node.value); nodes.push({ id, text: [ { key: null, value: node.value, type: node.type, }, ], width, height, path: getNodePath(node), ...appendParentKey(), }); } else { let t: string | [string, string][]; if (text.some(t => t.key !== null)) { t = text.map(t => { const keyStr = t.key === null ? "" : t.key; if (t.type === "object") return [keyStr, `{${t.childrenCount ?? 0} keys}`]; if (t.type === "array") return [keyStr, `[${t.childrenCount ?? 0} items]`]; if (t.value === null) return [keyStr, "null"]; return [keyStr, `${t.value}`]; }); } else { t = `${text[0].value}`; } const { width, height } = calculateNodeSize(t); nodes.push({ id, text, width, height, path: getNodePath(node), ...appendParentKey(), }); } return id; // Return the current id for referencing in the parent node } traverse(jsonTree); return { nodes, edges }; }; ================================================ FILE: apps/www/src/features/editor/views/GraphView/lib/utils/calculateNodeSize.ts ================================================ import { NODE_DIMENSIONS } from "../../../../../../constants/graph"; type Text = number | string | [string, string][]; type Size = { width: number; height: number }; const calculateLines = (text: Text): string => { if (Array.isArray(text)) { return text.map(([k, v]) => `${k}: ${JSON.stringify(v).slice(0, 80)}`).join("\n"); } return `${text}`; }; const calculateWidthAndHeight = (str: string, single = false) => { if (!str) return { width: 45, height: 45 }; const dummyElement = document.createElement("div"); dummyElement.style.whiteSpace = single ? "nowrap" : "pre-wrap"; dummyElement.innerText = str; dummyElement.style.fontSize = "12px"; dummyElement.style.width = "fit-content"; dummyElement.style.padding = "0 10px"; dummyElement.style.fontWeight = "500"; dummyElement.style.fontFamily = "monospace"; document.body.appendChild(dummyElement); const clientRect = dummyElement.getBoundingClientRect(); const lines = str.split("\n").length; const width = clientRect.width + 4; // Use parent height for single line nodes that are parents const height = single ? NODE_DIMENSIONS.PARENT_HEIGHT : lines * NODE_DIMENSIONS.ROW_HEIGHT; document.body.removeChild(dummyElement); return { width, height }; }; const sizeCache = new Map(); // clear cache every 2 mins setInterval(() => sizeCache.clear(), 120_000); export const calculateNodeSize = (text: Text, isParent = false) => { const cacheKey = [text, isParent].toString(); // check cache if data already exists if (sizeCache.has(cacheKey)) { const size = sizeCache.get(cacheKey); if (size) return size; } const lines = calculateLines(text); const sizes = calculateWidthAndHeight(lines, typeof text === "string"); if (isParent) sizes.width += 80; if (sizes.width > 700) sizes.width = 700; sizeCache.set(cacheKey, sizes); return sizes; }; ================================================ FILE: apps/www/src/features/editor/views/GraphView/lib/utils/getChildrenEdges.ts ================================================ import type { EdgeData, NodeData } from "jsoncrack-react"; export const getChildrenEdges = (nodes: NodeData[], edges: EdgeData[]): EdgeData[] => { const nodeIds = nodes.map(node => node.id); return edges.filter( edge => nodeIds.includes(edge.from as string) || nodeIds.includes(edge.to as string) ); }; ================================================ FILE: apps/www/src/features/editor/views/GraphView/lib/utils/getOutgoers.ts ================================================ import type { EdgeData, NodeData } from "jsoncrack-react"; type Outgoers = [NodeData[], string[]]; export const getOutgoers = ( nodeId: string, nodes: NodeData[], edges: EdgeData[], parent: string[] = [] ): Outgoers => { const outgoerNodes: NodeData[] = []; const matchingNodes: string[] = []; if (parent.includes(nodeId)) { const initialParentNode = nodes.find(n => n.id === nodeId); if (initialParentNode) outgoerNodes.push(initialParentNode); } const findOutgoers = (currentNodeId: string) => { const outgoerIds = edges.filter(e => e.from === currentNodeId).map(e => e.to); const nodeList = nodes.filter(n => { if (parent.includes(n.id) && !matchingNodes.includes(n.id)) matchingNodes.push(n.id); return outgoerIds.includes(n.id) && !parent.includes(n.id); }); outgoerNodes.push(...nodeList); nodeList.forEach(node => findOutgoers(node.id)); }; findOutgoers(nodeId); return [outgoerNodes, matchingNodes]; }; ================================================ FILE: apps/www/src/features/editor/views/GraphView/stores/useGraph.ts ================================================ import type { LayoutDirection, NodeData } from "jsoncrack-react"; import type { ViewPort } from "react-zoomable-ui"; import { create } from "zustand"; export interface Graph { viewPort: ViewPort | null; direction: LayoutDirection; fullscreen: boolean; selectedNode: NodeData | null; } const initialStates: Graph = { viewPort: null, direction: "RIGHT", fullscreen: false, selectedNode: null, }; interface GraphActions { setDirection: (direction: LayoutDirection) => void; setViewPort: (ref: ViewPort) => void; setSelectedNode: (nodeData: NodeData | null) => void; focusFirstNode: () => void; toggleFullscreen: (value: boolean) => void; zoomIn: () => void; zoomOut: () => void; centerView: () => void; } const useGraph = create((set, get) => ({ ...initialStates, setSelectedNode: nodeData => set({ selectedNode: nodeData }), setDirection: (direction = "RIGHT") => { set({ direction }); setTimeout(() => get().centerView(), 200); }, focusFirstNode: () => { const rootNode = document.querySelector("g[id$='node-1']"); get().viewPort?.camera?.centerFitElementIntoView(rootNode as HTMLElement, { elementExtraMarginForZoom: 100, }); }, zoomIn: () => { const viewPort = get().viewPort; viewPort?.camera?.recenter(viewPort.centerX, viewPort.centerY, viewPort.zoomFactor + 0.1); }, zoomOut: () => { const viewPort = get().viewPort; viewPort?.camera?.recenter(viewPort.centerX, viewPort.centerY, viewPort.zoomFactor - 0.1); }, centerView: () => { const viewPort = get().viewPort; viewPort?.updateContainerSize(); const canvas = document.querySelector(".jsoncrack-canvas") as HTMLElement | null; if (canvas) { viewPort?.camera?.centerFitElementIntoView(canvas); } }, toggleFullscreen: fullscreen => set({ fullscreen }), setViewPort: viewPort => set({ viewPort }), })); export default useGraph; ================================================ FILE: apps/www/src/features/editor/views/TreeView/Label.tsx ================================================ import React from "react"; import type { DefaultTheme } from "styled-components"; import { styled } from "styled-components"; import type { KeyPath } from "react-json-tree"; interface LabelProps { keyPath: KeyPath; nodeType: string; } function getLabelColor({ $type, theme }: { $type?: string; theme: DefaultTheme }) { if ($type === "Object") return theme.NODE_COLORS.PARENT_OBJ; if ($type === "Array") return theme.NODE_COLORS.PARENT_ARR; return theme.NODE_COLORS.PARENT_OBJ; } const StyledLabel = styled.span<{ $nodeType?: string }>` color: ${({ theme, $nodeType }) => getLabelColor({ theme, $type: $nodeType })}; &:hover { filter: brightness(1.5); transition: filter 0.2s ease-in-out; } `; export const Label = ({ keyPath, nodeType }: LabelProps) => { return {keyPath[0]}:; }; ================================================ FILE: apps/www/src/features/editor/views/TreeView/Value.tsx ================================================ import React from "react"; import type { DefaultTheme } from "styled-components"; import { useTheme } from "styled-components"; import { TextRenderer } from "../GraphView/CustomNode/TextRenderer"; type TextColorFn = { theme: DefaultTheme; $value?: string | unknown; }; function getValueColor({ $value, theme }: TextColorFn) { if ($value && !Number.isNaN(+$value)) return theme.NODE_COLORS.INTEGER; if ($value === "true") return theme.NODE_COLORS.BOOL.TRUE; if ($value === "false") return theme.NODE_COLORS.BOOL.FALSE; if ($value === "null") return theme.NODE_COLORS.NULL; // default return theme.NODE_COLORS.NODE_VALUE; } interface ValueProps { valueAsString: unknown; value: unknown; } export const Value = (props: ValueProps) => { const theme = useTheme(); const { valueAsString, value } = props; return ( {JSON.stringify(value)} ); }; ================================================ FILE: apps/www/src/features/editor/views/TreeView/index.tsx ================================================ import React from "react"; import { useTheme } from "styled-components"; import { JSONTree } from "react-json-tree"; import useJson from "../../../../store/useJson"; import { Label } from "./Label"; import { Value } from "./Value"; export const TreeView = () => { const theme = useTheme(); const json = useJson(state => state.json); return ( } labelRenderer={(keyPath, nodeType) =>