Compare commits

...
94 Commits
Author SHA1 Message Date
Aykut Saraç 6533e5746f Merge pull request #281 from AykutSarac/dependabot/npm_and_yarn/loader-utils-2.0.4
Bump loader-utils from 2.0.2 to 2.0.4
2022-12-30 18:22:38 +03:00
dependabot[bot] 5cc6d862e6 Bump loader-utils from 2.0.2 to 2.0.4
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 2.0.2 to 2.0.4.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v2.0.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v2.0.2...v2.0.4)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-30 15:18:56 +00:00
Aykut Saraç 1940987759 Merge pull request #280 from AykutSarac/mona-sans
Cloud updates
2022-12-30 18:15:46 +03:00
AykutSarac db0536a5e7 update user type 2022-12-30 18:12:10 +03:00
AykutSarac 2886488fd9 remove unused type 2022-12-30 18:09:35 +03:00
AykutSarac 7ad96fcf5b prettier 2022-12-30 18:06:37 +03:00
AykutSarac 3b47a36d23 Merge branch 'main' into mona-sans 2022-12-30 17:47:31 +03:00
AykutSarac d7bcc5ded4 update version 2022-12-30 17:46:00 +03:00
AykutSarac afce1006e5 use default avatar on error 2022-12-30 17:10:45 +03:00
AykutSarac c759853cd2 update env 2022-12-30 17:02:48 +03:00
AykutSarac 319abe039b update pricing styles 2022-12-30 16:49:41 +03:00
AykutSarac 321a13ab37 fix node width 2022-12-26 21:37:32 +03:00
AykutSarac 736fe68f2e prettier lint 2022-12-25 21:06:46 +03:00
AykutSarac 0195933c0a create pricing page 2022-12-25 21:05:41 +03:00
AykutSarac f42bbe6b83 refactor & delete json from list 2022-12-25 18:22:28 +03:00
AykutSarac df648c7809 lint 2022-12-25 18:22:01 +03:00
AykutSarac a2d77cf65a fix typoe 2022-12-25 18:21:57 +03:00
AykutSarac 1e6c7b6cb2 warn unsaved changes 2022-12-25 15:35:44 +03:00
AykutSarac 2c53a3af64 increase canvas ratio for change watch 2022-12-24 23:05:54 +03:00
AykutSarac 8070c7c280 api improvements & document count limit 2022-12-24 22:55:54 +03:00
AykutSarac 418031a3c8 hide public/private from non owners 2022-12-23 20:10:11 +03:00
AykutSarac 810f0d72be add auth for get json 2022-12-23 19:12:51 +03:00
AykutSarac 0f76b4ad4f styling improvements 2022-12-23 16:13:52 +03:00
AykutSarac 35382609a4 rename embed page to docs 2022-12-23 13:39:05 +03:00
AykutSarac abf0c0856f create embed docs 2022-12-23 13:36:32 +03:00
AykutSarac 8c3ae89355 create embed guide 2022-12-23 00:13:30 +03:00
AykutSarac 458723b2d6 improve embeds 2022-12-22 22:13:40 +03:00
AykutSarac 1548d6d4dd update error page img 2022-12-22 22:13:24 +03:00
AykutSarac f0399b68a5 code refactor sidebar & cloud modal 2022-12-20 11:00:34 +03:00
AykutSarac ca9cd881c4 update login button text 2022-12-18 18:48:50 +03:00
AykutSarac fd58197035 improve parser & add image preview setting 2022-12-18 15:24:32 +03:00
AykutSarac 7da91633a5 prettier lint 2022-12-18 14:14:07 +03:00
AykutSarac 6456e41671 fix expand/collapse all 2022-12-18 13:59:25 +03:00
AykutSarac ebe39c2210 fix unauthorized json access 2022-12-17 23:43:07 +03:00
AykutSarac 05869e63db prettier lint 2022-12-17 23:06:12 +03:00
AykutSarac 9265d45e79 fix incorrect state 2022-12-17 23:04:08 +03:00
AykutSarac 7eb5edf550 remove unused function 2022-12-17 23:02:02 +03:00
AykutSarac 20bcdfef10 replace useConfig with useGraph 2022-12-17 23:00:37 +03:00
AykutSarac a26b94f584 improve parsing structure 2022-12-17 22:27:48 +03:00
Aykut Saraç f8a8747775 style responsive design 2022-12-12 10:52:20 +03:00
AykutSarac 4d0386b32e lazy load image 2022-12-11 22:37:47 +03:00
AykutSarac 9f51259084 update doc name 2022-12-11 19:37:48 +03:00
AykutSarac 6c270635ac fix toggle private 2022-12-11 18:27:22 +03:00
AykutSarac 4ed3411f86 styling improvements 2022-12-11 14:27:07 +03:00
AykutSarac 8225bc2986 improve parsing ui 2022-12-10 17:08:42 +03:00
AykutSarac 5da20fa5be Merge branch 'main' into mona-sans 2022-12-08 19:56:49 +03:00
AykutSarac d5db8a2df0 update folder structure 2022-12-08 19:52:16 +03:00
Aykut Saraç da620a17cb Update README.md 2022-12-08 11:51:55 +03:00
AykutSarac 9bcd86f88a update homepage embed options 2022-12-08 00:14:01 +03:00
AykutSarac a8167f53d6 update folder structure 2022-12-08 00:11:44 +03:00
AykutSarac 3feb5983a4 fix homepage 2022-12-08 00:08:23 +03:00
AykutSarac ba02b60e06 add options to embed 2022-12-07 23:37:32 +03:00
AykutSarac eb73508ab7 create altogic connections 2022-12-07 22:34:12 +03:00
AykutSarac 59789aaeb1 refactor: improve parsing logic 2022-12-04 00:12:16 +03:00
AykutSarac cf575c690a style font size 2022-12-03 12:59:56 +03:00
AykutSarac 1b37e812ff implement font mona-sans 2022-12-02 21:36:16 +03:00
Aykut Saraç debe13fef5 update version 2022-11-24 13:12:02 +03:00
Aykut Saraç 4864d35d51 Merge pull request #264 from AykutSarac/style-object-array
seperate colors from array and object
2022-11-24 13:08:30 +03:00
Aykut Saraç dface28f1f remove readonly type 2022-11-24 13:04:54 +03:00
Aykut Saraç c8134fcda5 seperate colors from array and object 2022-11-24 12:56:55 +03:00
AykutSarac b02f7ccb99 add generic support store hooks 2022-11-20 11:59:28 +03:00
AykutSarac d0e11cc5c1 feat: replace powered by 2022-11-13 21:46:23 +03:00
Aykut Saraç 57389b3837 Merge pull request #251 from victorbrambati/main
fix empty nodes
2022-11-07 10:43:27 +03:00
AykutSarac 965e070ca7 style: resize sponsors section 2022-11-07 08:11:20 +03:00
victorbrambati 41743f1ff0 chore: refactor getOutgoers 2022-11-06 18:37:40 -03:00
victorbrambati b7492c1c55 fix: collapsed parent node disappears when expand 2022-11-06 18:22:19 -03:00
AykutSarac edff409b7d refactor(validateToken): code refactor 2022-11-06 13:06:21 +03:00
AykutSarac a8df2c20aa feat: create account view after logged in 2022-11-06 13:05:21 +03:00
AykutSarac 21717be7f3 feat: implement google auth 2022-11-05 21:19:41 +03:00
AykutSarac 9e9b9e5481 feat: create layout 2022-11-05 16:09:46 +03:00
Aykut Saraç 94bc4275ec Merge pull request #252 from AykutSarac/code-refactor
chore: code refactor
2022-11-05 13:09:30 +03:00
victorbrambati 36fcb6f55b feat: don't create an empty node in a root array 2022-11-04 17:12:10 -03:00
victorbrambati e772153cd3 fix: different brothers node in array is not work correctly 2022-11-03 19:42:01 -03:00
victorbrambati 76bcad4e1d fix: different brother node for same parent object 2022-11-02 22:39:25 -03:00
AykutSarac 15f6dd2545 chore: code refactor 2022-11-01 22:44:54 +03:00
victorbrambati 50cb490dbe feat: brothers node and parent node in the same parent 2022-11-01 09:45:06 -03:00
AykutSarac d3bbc6d813 fix: canvas scaling 2022-11-01 12:18:12 +03:00
victorbrambati 24d9715056 fix empty nodes 2022-10-30 22:47:48 -03:00
AykutSarac fb6d8a55f5 feat: replace twitter embed with official 2022-10-30 15:59:45 +03:00
AykutSarac f8e5f93c6e chore: update docs 2022-10-30 15:44:35 +03:00
AykutSarac 2e93da574d fix: remove collapsed state persisting on json change 2022-10-30 15:44:25 +03:00
AykutSarac 96748179f7 update version 2022-10-30 15:39:37 +03:00
AykutSarac 7dfe1c5df2 update preview image 2022-10-30 15:36:28 +03:00
AykutSarac 9449e6b006 fix incorrect state fold/unfold 2022-10-30 15:30:34 +03:00
AykutSarac 7774e685b4 feat: improve collapse/expand all & rename fold/unfold 2022-10-30 15:19:03 +03:00
Aykut Saraç 86f0f0d5ec Merge pull request #250 from AykutSarac/fix/canvas-pan
fix: #158
2022-10-30 13:34:12 +03:00
AykutSarac 8fa8130d9d fix: #158 2022-10-30 13:32:20 +03:00
Aykut Saraç e32147e1bf Merge pull request #210 from TallTed/patch-1
improve language of "huge file" error
2022-10-30 13:22:56 +03:00
Aykut Saraç 3f17281a80 fix: widget src 2022-10-28 15:38:20 +03:00
Aykut Saraç c055e14fc8 feat: improve loading state 2022-10-28 15:24:48 +03:00
Aykut Saraç 35aa992375 feat: bug fixes & deprecation warning 2022-10-28 14:44:55 +03:00
AykutSarac 0468180440 update packages 2022-10-26 21:17:34 +03:00
AykutSarac 884a2ad1ec add page title 2022-10-26 20:44:34 +03:00
Ted Thibodeau Jr c11779eaba improve language of "huge file" error 2022-09-28 10:49:26 -04:00
81 changed files with 3249 additions and 1484 deletions

No files matched your search

+3 -1
View File
@@ -1 +1,3 @@
NEXT_PUBLIC_BASE_URL=http://localhost:3000
NEXT_PUBLIC_BASE_URL=http://localhost:3000
NEXT_PUBLIC_ALTOGIC_ENV_URL=https://jsoncrack.c5-na.altogic.com
NEXT_PUBLIC_ALTOGIC_CLIENT_KEY=f1e92022789f4ccf91273a345ab2bdf8
+3 -1
View File
@@ -1 +1,3 @@
NEXT_PUBLIC_BASE_URL=https://jsoncrack.com
NEXT_PUBLIC_BASE_URL=https://jsoncrack.com
NEXT_PUBLIC_ALTOGIC_ENV_URL=https://jsoncrack.c5-na.altogic.com
NEXT_PUBLIC_ALTOGIC_CLIENT_KEY=f1e92022789f4ccf91273a345ab2bdf8
+1 -1
View File
@@ -2,7 +2,7 @@
"trailingComma": "es5",
"singleQuote": false,
"semi": true,
"printWidth": 85,
"printWidth": 100,
"arrowParens": "avoid",
"importOrder": [
"^(react/(.*)$)|^(react$)",
-2
View File
@@ -21,8 +21,6 @@ To get an overview of the project, read the [README](README.md). Here are some r
Join the [Discord channel](https://discord.gg/yVyTtCRueq). If you have questions or feedback, this is the best place to reach the team and other contributors directly.
If you're already familar with Open Source contributions, we **highly recommend** you to take a look at our [TODO List](/TODO.md).
### Issues
#### Create a new issue
+18 -32
View File
@@ -1,33 +1,15 @@
<center>
<div align="center" style="display:flex;flex-direction:column;">
<a href="https://jsoncrack.com">
<img width="1080" alt="jsoncrack" src="https://user-images.githubusercontent.com/47941171/187418000-8edea92b-b3ac-4b07-9c4c-e42f6763817d.png">
<img width="700" alt="jsoncrack" src="https://user-images.githubusercontent.com/47941171/206401172-74c21f7f-0a32-4532-96cc-4cf6b493c837.png">
</a>
</center>
<p>
<p align="center">
<a href="https://discord.gg/yVyTtCRueq">
<img alt="github sponsors" src="https://dcbadge.vercel.app/api/server/yVyTtCRueq?style=flat-square" />
</a>
<a href="https://app.travis-ci.com/github/AykutSarac/jsoncrack.com">
<img alt="travis ci badge" src="https://img.shields.io/travis/com/AykutSarac/jsoncrack.com/main?style=flat-square" />
</a>
<a href="https://github.com/AykutSarac/jsoncrack.com/blob/main/LICENSE">
<img alt="license badge" src="https://img.shields.io/github/license/AykutSarac/jsoncrack.com?style=flat-square" />
</a>
<a href="https://github.com/AykutSarac/jsoncrack.com/releases">
<img alt="version badge" src="https://img.shields.io/github/package-json/v/AykutSarac/jsoncrack.com?color=brightgreen&style=flat-square" />
</a>
<a href="https://github.com/sponsors/AykutSarac">
<img alt="github sponsors" src="https://img.shields.io/github/sponsors/AykutSarac?style=flat-square" />
</a>
</p>
<h3>“Explore, analyze and understand even most complex JSON structures.”</br>Unlock the full potentiel of your data.</h3>
<p align="center">
<i>Simple json visualization tool for your data.</i>
<p align="center">
<a href="https://www.producthunt.com/posts/json-crack?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-json&#0045;crack" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=332281&theme=light" alt="JSON&#0032;Crack - Simple&#0032;visualization&#0032;tool&#0032;for&#0032;your&#0032;JSON&#0032;data&#0046; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
</p>
<a href="https://www.producthunt.com/posts/json-crack?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-json&#0045;crack" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=332281&theme=light" alt="JSON&#0032;Crack - Simple&#0032;visualization&#0032;tool&#0032;for&#0032;your&#0032;JSON&#0032;data&#0046; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<a href="https://discord.gg/yVyTtCRueq" target="_blank"><img src="https://user-images.githubusercontent.com/47941171/206397224-94da03a4-59d0-48cd-aafc-512624a768d6.png" "style=" height: 54px;" height="54" /></a>
</br>
<a href="https://github.com/sponsors/AykutSarac" target="_blank"><img src="https://user-images.githubusercontent.com/47941171/206397875-a4e73f02-5d8f-4db0-902b-9a4bc2b22d90.png" "style=" height: 54px;" height="54" /></a>
</p>
</div>
<p align="center">
<img width="800" src="./public/assets/jsoncrack-screenshot.webp" alt="preview 1" />
@@ -35,21 +17,25 @@
# JSON Crack (jsoncrack.com)
JSON Crack is a tool that generates graph diagrams from JSON objects. These diagrams are much easier to navigate than the textual format and to make it even more convenient, the tool also allows you to search the nodes. Additionally, the generated diagrams can also be downloaded or clipboard as image.
You can use the web version at [jsoncrack.com](https://jsoncrack.com) or also run it locally as [Docker container](https://github.com/AykutSarac/jsoncrack.com#-docker).
Introducing JSON Crack – the open-source, free JSON visualization app that will revolutionize the way you work with data. With its intuitive and user-friendly interface, JSON Crack makes it easy to explore, analyze, and understand even the most complex JSON structures. Whether you're a developer working on a large-scale project or a data enthusiast looking to uncover hidden insights, JSON Crack has the tools and features you need to unlock the full potential of your data. Best of all, because JSON Crack is open-source and free, you can use it without breaking the bank. Try JSON Crack today and experience the power of data visualization like never before.
> <b><a href="https://jsoncrack.com">JSON Crack - Crack your data into pieces</a></b>
## ⚡️ Features
## ⚡️ Key Features
- Search Nodes
- Share links & Create Embed Widgets
- Download/Clipboard as image
- Upload JSON locally or fetch from URL
- Great UI/UX
- User-friendly Interface
- Light/Dark Mode
- Advanced Error Messages
## ⭐️ Embedding Into Your Website
You can use the JSON Crack to visualize your JSON **at your products or websites**, see our very simple guide: https://jsoncrack.com/embed
You can choose to **[partner us](https://github.com/sponsors/AykutSarac)** to remove attribute for your commercial products.
<img width="291" alt="Screenshot_2022-12-08_at_11 46 02-removebg-preview" src="https://user-images.githubusercontent.com/47941171/206400503-150f60b6-f4b3-4649-854d-be4a7b826275.png">
## 🛠 Development Setup
-16
View File
@@ -1,16 +0,0 @@
## Feature:
- [ ] [YAML Support - #161](https://github.com/AykutSarac/jsoncrack.com/issues/161)
- [ ] [VS Code extension](https://github.com/AykutSarac/jsoncrack.com/discussions/130)
## Enhancements:
- [ ] Clear rewrite of [parsing algorithm](/src/utils/jsonParser.ts)
- [ ] Performance Optimization
- [ ] Fix `useInViewport` hook for performance
- [ ] Canvas should fit into view on click `Center` icon at toolbar
## Fix:
- [ ] Handle browser freezing while parsing JSON & painting DOM
- [ ] [Incorrect Arrow View - #155](https://github.com/AykutSarac/jsoncrack.com/issues/155)
- [ ] [Empty Nodes - #27](https://github.com/AykutSarac/jsoncrack.com/issues/27)
+1 -7
View File
@@ -9,13 +9,7 @@ const withPWA = require("next-pwa")({
* @type {import('next').NextConfig}
*/
const nextConfig = {
reactStrictMode: true,
exportPathMap: async () => ({
"/": { page: "/" },
"/editor": { page: "/Editor" },
"/widget": { page: "/Widget" },
"/embed": { page: "/Embed" },
}),
reactStrictMode: false,
};
module.exports = withPWA(nextConfig);
+17 -8
View File
@@ -1,7 +1,7 @@
{
"name": "json-crack",
"private": true,
"version": "v2.0.4",
"version": "v2.5.0",
"author": "https://github.com/AykutSarac",
"homepage": "https://jsoncrack.com",
"scripts": {
@@ -14,32 +14,41 @@
},
"dependencies": {
"@monaco-editor/react": "^4.4.6",
"@sentry/nextjs": "^7.13.0",
"@react-oauth/google": "^0.4.0",
"@sentry/nextjs": "^7.16.0",
"@tanstack/react-query": "^4.19.1",
"allotment": "^1.17.0",
"compress-json": "^2.1.2",
"altogic": "^2.3.8",
"axios": "^1.1.3",
"dayjs": "^1.11.6",
"html-to-image": "^1.10.8",
"jsonc-parser": "^3.2.0",
"lodash.debounce": "^4.0.8",
"lz-string": "^1.4.4",
"next": "^12.3.1",
"next-transpile-modules": "^9.0.0",
"next-transpile-modules": "^9.1.0",
"react": "^18.2.0",
"react-color": "^2.19.3",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.0",
"react-icons": "^4.4.0",
"react-icons": "^4.6.0",
"react-in-viewport": "^1.0.0-alpha.28",
"react-linkify-it": "^1.0.7",
"react-twitter-embed": "^4.0.4",
"react-syntax-highlighter": "^15.5.0",
"react-zoom-pan-pinch": "^2.1.3",
"reaflow": "^5.0.7",
"styled-components": "^5.3.5",
"zustand": "^4.1.1"
"styled-components": "^5.3.6",
"zustand": "^4.1.3"
},
"devDependencies": {
"@testing-library/react": "^13.3.0",
"@trivago/prettier-plugin-sort-imports": "^3.3.0",
"@types/lodash.debounce": "^4.0.7",
"@types/lz-string": "^1.3.34",
"@types/node": "^18.7.21",
"@types/react": "18.0.21",
"@types/react-color": "^3.0.6",
"@types/react-syntax-highlighter": "^15.5.5",
"@types/styled-components": "^5.1.26",
"eslint": "8.24.0",
"eslint-config-next": "12.3.1",
+1 -1
View File
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" width="982.48732" height="763.01413" viewBox="0 0 982.48732 763.01413" xmlns:xlink="http://www.w3.org/1999/xlink"><circle cx="559.4329" cy="333.06116" r="40.02979" fill="#e6e6e6"/><path d="M766.295,452.99465l-41.832-41.832a44.307,44.307,0,0,0-14.42627-61.92578,44.45758,44.45758,0,0,0-58.91358,12.23779,44.30652,44.30652,0,0,0,60.03321,62.99512l41.832,41.832A9.40939,9.40939,0,0,0,766.295,452.99465ZM709.59431,409.6011a31.68421,31.68421,0,1,1,9.27978-22.40381A31.58568,31.58568,0,0,1,709.59431,409.6011Z" transform="translate(-108.75634 -68.49293)" fill="#3f3d56"/><path d="M1069.529,514.69884C1176.50914,633.00725,866.39184,803.50707,615.8095,803.50707S191.86334,671.39983,162.09,514.69884C89.89987,134.75174,777.16735-106.74054,615.8095,225.89055,380.61181,710.73882,985.27984,421.5284,1069.529,514.69884Z" transform="translate(-108.75634 -68.49293)" fill="#e6e6e6"/><path d="M1021.529,542.69884C1128.50914,661.00725,818.39184,831.50707,567.8095,831.50707S143.86334,699.39983,114.09,542.69884C41.89987,162.75174,729.16735-78.74054,567.8095,253.89055,332.61181,738.73882,937.27984,449.5284,1021.529,542.69884Z" transform="translate(-108.75634 -68.49293)" fill="#3f3d56"/><path d="M258.35026,265.31823c-75.06741-3.30525-133.27133-19.49788-132.50668-36.86436.45844-10.41178,21.70324-18.7842,58.28677-22.96955a1,1,0,1,1,.22727,1.987c-34.46168,3.943-56.11733,12.01678-56.516,21.07051-.69148,15.70451,59.11433,31.63093,130.59659,34.77832s132.4564-7.461,133.14787-23.16554c.40032-9.09178-20.59681-19.06558-54.79775-26.0284a1.00008,1.00008,0,1,1,.3991-1.95993c36.3011,7.39058,56.85694,17.624,56.39672,28.0763C392.81953,257.60908,333.41767,268.62348,258.35026,265.31823Z" transform="translate(-108.75634 -68.49293)" fill="#7289da"/><path d="M191.67927,268.22672a76.98277,76.98277,0,0,0,133.03578,5.52178A1211.97551,1211.97551,0,0,1,191.67927,268.22672Z" transform="translate(-108.75634 -68.49293)" fill="#7289da"/><path d="M333.019,252.71347a76.997,76.997,0,1,0-147.74945-5.70562A572.23325,572.23325,0,0,0,333.019,252.71347Z" transform="translate(-108.75634 -68.49293)" fill="#7289da"/><circle cx="168.83353" cy="123.60073" r="9" fill="#fff"/><circle cx="122.51347" cy="152.59128" r="15" fill="#fff"/><circle cx="348.90479" cy="142.89813" r="9.99975" fill="#7289da"/><circle cx="122.90479" cy="452.89813" r="6.00007" fill="#e6e6e6"/><circle cx="542.90479" cy="658.89813" r="6.00007" fill="#e6e6e6"/><circle cx="151.90479" cy="339.89813" r="3.00007" fill="#e6e6e6"/><circle cx="399.90479" cy="563.89813" r="3.00007" fill="#e6e6e6"/><circle cx="850.90479" cy="534.89813" r="3.00007" fill="#e6e6e6"/><circle cx="709.90479" cy="623.89813" r="3.00007" fill="#e6e6e6"/><circle cx="562.90479" cy="515.89813" r="3.00007" fill="#e6e6e6"/><circle cx="372.90479" cy="219.89813" r="3.00007" fill="#e6e6e6"/><circle cx="396.90479" cy="71.89813" r="3.00007" fill="#e6e6e6"/><circle cx="382.90479" cy="694.89813" r="3.00007" fill="#e6e6e6"/><circle cx="234.90479" cy="631.89813" r="3.00007" fill="#e6e6e6"/><circle cx="703.90479" cy="523.89813" r="3.00007" fill="#e6e6e6"/><circle cx="148.3737" cy="224.7987" r="9" fill="#fff"/><path d="M460.6612,431.39114c0,26.88035-41.55939,89.516-53.70465,107.24669a3.99188,3.99188,0,0,1-6.5907,0c-12.14526-17.73066-53.70465-80.36634-53.70465-107.24669a57,57,0,1,1,114,0Z" transform="translate(-108.75634 -68.49293)" fill="#fff"/><circle cx="294.90486" cy="358.8982" r="29" fill="#7289da"/><ellipse cx="294.40486" cy="496.3982" rx="33.5" ry="6.5" fill="#fff"/><path d="M925.60957,386.41245a10.52681,10.52681,0,0,0-.64106,1.52851l-47.783,13.27682-8.61085-8.42662L854.1589,404.12429,867.953,420.52338a8,8,0,0,0,9.35283,2.169l51.61632-22.78484a10.49709,10.49709,0,1,0-3.31256-13.49509Z" transform="translate(-108.75634 -68.49293)" fill="#ffb8b8"/><path d="M875.50191,401.996l-17.9352,13.95958a4.5,4.5,0,0,1-6.68163-1.33719L839.73843,394.891a12.49741,12.49741,0,0,1,19.76923-15.29426l16.33819,15.59379a4.5,4.5,0,0,1-.34394,6.80543Z" transform="translate(-108.75634 -68.49293)" fill="#7289da"/><path d="M930.19158,379.80967l18.18807,8.1829a3.14913,3.14913,0,0,1,1.57976,4.16384l-2.15339,4.78633a1.05293,1.05293,0,0,1,.52659,1.38795l-.86136,1.91453a1.05292,1.05292,0,0,1-1.388.52659l-.86136,1.91453a1.05292,1.05292,0,0,1,.52659,1.388l-.86136,1.91453a1.0529,1.0529,0,0,1-1.38794.52659L931.8709,432.36162a3.14914,3.14914,0,0,1-4.16384,1.57977l-18.18807-8.1829a3.14915,3.14915,0,0,1-1.57977-4.16384l18.08852-40.20522A3.14913,3.14913,0,0,1,930.19158,379.80967Z" transform="translate(-108.75634 -68.49293)" fill="#3f3d56"/><path d="M912.82137,423.21181l14.59056,6.57106a3.03424,3.03424,0,0,0,3.99937-1.51155l6.73892-14.98951,2.34078-5.196,6.09875-13.562a3.026,3.026,0,0,0-1.51149-3.98886l-4.58726-2.06786L930.47694,383.964a3.02112,3.02112,0,0,0-3.98861,1.522L920.589,398.59669l-6.7179,14.92653-2.56118,5.69979A3.022,3.022,0,0,0,912.82137,423.21181Z" transform="translate(-108.75634 -68.49293)" fill="#f2f2f2"/><polygon points="773.577 Line truncated
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-name="Layer 1" width="1118.03" height="826.45" viewBox="0 0 1118.03 826.45"><defs><linearGradient id="2c65cc85-6ec1-454a-a801-b7854259077c-180" x1="377.53" y1="611.1" x2="377.53" y2="84.18" gradientTransform="translate(256.63 -110.12) rotate(27.45)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="gray" stop-opacity="0.25"/><stop offset="0.54" stop-color="gray" stop-opacity="0.12"/><stop offset="1" stop-color="gray" stop-opacity="0.1"/></linearGradient></defs><title>startled</title><ellipse cx="741.34" cy="764.2" rx="314.09" ry="31.67" fill="#dadaf9" opacity="0.1"/><path d="M690.57,153c-71.67-2.08-139.94-21.34-205.12-42.42S355.69,66.17,285.73,55c-45-7.18-96.45-8.2-132.71,11.89-34.88,19.32-46.16,52.69-52.22,83.64-4.56,23.29-7.24,47.81,5.25,69.61,8.68,15.14,24.08,27.86,34.73,42.36,37.07,50.46,10.87,112.67-29.3,161.93-18.84,23.11-40.69,45.16-55.23,69.76S35,547,47.71,572.07c12.61,24.87,42.65,43.55,75.2,56.69,66.1,26.69,144,34.33,220,38.65C511,677,680,672.84,848.63,668.69c62.39-1.53,125-3.09,186.43-11.12,34.08-4.46,69.27-11.54,94-28.63,31.42-21.7,39.2-58.43,18.15-85.64-35.3-45.63-132.92-57-157.6-105.93-13.58-26.95.37-57,20.11-82,42.34-53.62,113.32-100.67,117.06-162,2.57-42.1-31.55-84.26-84.31-104.18-55.3-20.88-132-18.25-172.79,16.31C827.68,141.14,753.86,154.83,690.57,153Z" transform="translate(-40.99 -36.78)" fill="#dadaf9" opacity="0.1"/><path d="M213.3,663c-7.92-3.53-1.22-41.88,16-101,0,0,0,0,0,0,11-37.93,26.38-84.41,45-135.71,1.34-3.68,2.7-7.37,4.06-11.1q2.3-6.27,4.66-12.64l.14-.35c.22-.61.45-1.21.67-1.81q8.94-24,18.69-49.11c.12-.34.25-.67.38-1l.33-.85c1.56-4,3.14-8,4.73-12.07.17-.42.33-.86.5-1.28.23-.59.46-1.16.68-1.74.08-.18.14-.37.22-.53.13-.35.27-.69.4-1,1.6-4,3.19-8.05,4.78-12q3.9-9.7,7.74-19.21.67-1.67,1.35-3.3,1.93-4.79,3.87-9.51c.5-1.2,1-2.41,1.48-3.6.38-.91.75-1.83,1.12-2.73q6.09-14.73,12.1-28.89a.17.17,0,0,0,0-.1c15.25-35.93,30-68.92,43.7-97.76.08-.15.14-.31.22-.46l1.45-3.06c35.16-73.71,62.82-119.2,72.17-115,9.62,4.28-2.36,60.14-28.61,142.26-.34,1.09-.69,2.16-1.05,3.25a5.8,5.8,0,0,1-.2.63q-2,6.38-4.21,13c-.08.26-.17.52-.26.78q-5,15.3-10.64,31.6c-.51,1.46-1,2.95-1.54,4.43-5.8,16.79-12,34.27-18.65,52.29h0c-.4,1.11-.81,2.21-1.22,3.33h0c-6.65,18.05-13.66,36.63-21,55.59h0c-.47,1.2-.94,2.41-1.4,3.62h0c-.1.26-.2.51-.31.77q-2.75,7.05-5.53,14.16c-.53,1.32-1,2.64-1.56,3.95-1.32,3.35-2.64,6.7-4,10-.11.3-.23.6-.35.89s-.19.47-.29.7c-.22.58-.45,1.15-.68,1.72-.62,1.55-1.23,3.1-1.85,4.63-1.07,2.68-2.14,5.33-3.21,8l-.33.8v0a.76.76,0,0,1,0,.11q-6.52,16.11-13,31.67h0q-2.18,5.25-4.35,10.43v0c-1.07,2.55-2.13,5.09-3.2,7.6-7.94,18.77-15.74,36.74-23.34,53.77l0,0C263,600,224.64,668.06,213.3,663Z" transform="translate(-40.99 -36.78)" fill="#dadaf9" opacity="0.5"/><path d="M232.07,591.61c-4.88-2.1,1.18-29.38,14.92-71.76l0,0c8.8-27.18,20.77-60.58,35-97.52l3.11-8q1.75-4.51,3.56-9.1a2.77,2.77,0,0,0,.1-.26l.52-1.3q6.81-17.33,14.21-35.4c.09-.25.19-.48.29-.72s.16-.41.25-.61c1.18-2.9,2.37-5.79,3.57-8.71l.39-.93.51-1.25c.06-.13.11-.27.16-.39s.21-.49.31-.73q1.82-4.38,3.61-8.69,2.94-7,5.83-13.87l1-2.38c1-2.31,1.94-4.59,2.91-6.87l1.11-2.6c.29-.66.56-1.32.84-2q4.57-10.63,9.07-20.88a.14.14,0,0,0,0-.07c11.4-26,22.36-49.86,32.42-70.77.06-.11.11-.23.17-.34l1.06-2.22c25.86-53.47,45.62-86.74,51.38-84.25,5.93,2.56-4.33,42.39-24.9,101.35l-.82,2.34c-.05.15-.1.3-.16.45q-1.6,4.58-3.29,9.31l-.2.56q-3.93,11-8.27,22.71c-.39,1.06-.78,2.13-1.19,3.19-4.48,12.07-9.27,24.66-14.33,37.64h0l-.93,2.39h0q-7.62,19.5-16,40.06h0c-.35.87-.7,1.74-1.06,2.61h0c-.08.19-.16.37-.23.56l-4.19,10.21-1.18,2.85c-1,2.42-2,4.84-3,7.23-.08.22-.17.43-.26.64s-.14.34-.22.51l-.51,1.23c-.47,1.12-.93,2.24-1.4,3.35L333.88,395l-.25.58h0l0,.08q-4.92,11.64-9.76,22.87h0q-1.63,3.8-3.27,7.54s0,0,0,0q-1.2,2.78-2.4,5.5c-5.94,13.56-11.75,26.57-17.39,38.89l0,0C266.66,545,239.06,594.63,232.07,591.61Z" transform="translate(-40.99 -36.78)" fill="#dadaf9" opacity="0.5"/><path d="M553.79,389.26c0-2.79-2.73-1.35-3.11-1.23h0a19,19,0,0,0-3.56-2.31c-6.09-3.17-15.29-5.1-22.62-6.23a.12.12,0,0,0-.08,0c-6.46-1-11.44-1.34-11.44-1.34l-3.81.05-1.42,0a4,4,0,0,1,2.08-1.57c1.06-.13-4.48-.54-7.11-.72,0,0,0,0,0,0h-.11l-1.22-.08c-5.78,1.36-16.61-6.07-16.61-6.07l-1.26-6.34c4.28-4.63,3.66-16.2,3.66-16.2s.36-3.88,4-9.22-.61-8.72-.61-8.72l6.29-6.71a9,9,0,0,1,1.14-6.83h0a6.13,6.13,0,0,1,1.41-1.48l0-.05.1-.09c4.11-3.15,7.58-14.09,7.58-14.09s-.8-1.95,3.7-7.24l0-.06c.6-.7,1.3-1.45,2.11-2.29l0,0c0-.07.12-.13.18-.2C520.3,283,517.46,279,517.46,279l-8.77-10.66c2.07-3.46-14.55-28.29-14.55-28.29s-2.12-5.11-.92-10a7,7,0,0,0,.1-3.54,4,4,0,0,0-.94-1.77,3.07,3.07,0,0,0-1-.71l-.6-.05.71-.52c.29-.19.57-.4.86-.6.62.24,1.24.45,1.87.64a38.25,38.25,0,0,0,36.06-6.86c.25-.19.49-.4.72-.61s.63-.54.94-.83c.14-.13.28-.25.4-.38s.47-.46.7-.7c.07.22.14.43.22.65a19.34,19.34,0,0,0,1.13,2.58,1.59,1.59,0,0,0,.13.22.41.41,0,0,0,0,.09c.07.15.16.29.24.43a12.68,12.68,0,0,0,2.06,2.6,6.48,6.48,0,0,0,.62.54,6.85,6.85,0,0,0,1,.71,1,1,0,0,0,.17.12,1.22,1.22,0,0,0,Line truncated

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 158 KiB

+13
View File
@@ -0,0 +1,13 @@
import { APIError, createClient } from "altogic";
let envUrl = process.env.NEXT_PUBLIC_ALTOGIC_ENV_URL as string;
let clientKey = process.env.NEXT_PUBLIC_ALTOGIC_CLIENT_KEY as string;
const altogic = createClient(envUrl, clientKey);
export interface AltogicResponse<T> {
data: T;
errors: APIError | null;
}
export { altogic };
+11 -7
View File
@@ -4,6 +4,7 @@ import styled, { DefaultTheme } from "styled-components";
enum ButtonType {
PRIMARY = "PRIMARY",
SECONDARY = "BLURPLE",
TERTIARY = "PURPLE",
DANGER = "DANGER",
SUCCESS = "SEAGREEN",
WARNING = "ORANGE",
@@ -16,7 +17,7 @@ interface ButtonProps {
type ConditionalProps =
| ({
link?: boolean;
link: boolean;
} & React.ComponentPropsWithoutRef<"a">)
| ({
link?: never;
@@ -29,19 +30,20 @@ function getButtonStatus(status: keyof typeof ButtonType, theme: DefaultTheme) {
const StyledButton = styled.button<{
status: keyof typeof ButtonType;
block: boolean;
link: boolean;
}>`
display: flex;
display: inline-flex;
align-items: center;
background: ${({ status, theme }) => getButtonStatus(status, theme)};
color: #ffffff;
padding: 8px 16px;
min-width: 60px;
padding: ${({ link }) => (link ? "2px 16px" : "8px 16px")};
min-width: 70px;
min-height: 32px;
border-radius: 3px;
font-family: "Mona Sans";
font-size: 14px;
font-weight: 500;
font-family: "Catamaran", sans-serif;
width: ${({ block }) => (block ? "100%" : "fit-content")};
width: ${({ block }) => (block ? "-webkit-fill-available" : "fit-content")};
height: 40px;
background-image: none;
@@ -73,6 +75,7 @@ const StyledButtonContent = styled.div`
gap: 8px;
white-space: nowrap;
text-overflow: ellipsis;
font-weight: 600;
`;
export const Button: React.FC<ButtonProps & ConditionalProps> = ({
@@ -84,10 +87,11 @@ export const Button: React.FC<ButtonProps & ConditionalProps> = ({
}) => {
return (
<StyledButton
as={link ? "a" : "button"}
type="button"
as={link ? "a" : "button"}
status={status ?? "PRIMARY"}
block={block}
link={link}
{...props}
>
<StyledButtonContent>{children}</StyledButtonContent>
+5 -20
View File
@@ -1,7 +1,7 @@
import React from "react";
// import { useInViewport } from "react-in-viewport";
import { CustomNodeProps } from "src/components/CustomNode";
import useConfig from "src/hooks/store/useConfig";
import useGraph from "src/store/useGraph";
import * as Styled from "./styles";
const inViewport = true;
@@ -9,28 +9,16 @@ const inViewport = true;
const ObjectNode: React.FC<CustomNodeProps> = ({ node, x, y }) => {
const { text, width, height, data } = node;
const ref = React.useRef(null);
const performanceMode = useConfig(state => state.performanceMode);
const performanceMode = useGraph(state => state.performanceMode);
// const { inViewport } = useInViewport(ref);
if (data.isEmpty) return null;
return (
<Styled.StyledForeignObject
width={width}
height={height}
x={0}
y={0}
ref={ref}
isObject
>
<Styled.StyledForeignObject width={width} height={height} x={0} y={0} ref={ref} isObject>
{(!performanceMode || inViewport) &&
text.map((val, idx) => (
<Styled.StyledRow
data-key={JSON.stringify(val[1])}
data-x={x}
data-y={y}
key={idx}
>
<Styled.StyledRow data-key={JSON.stringify(val[1])} data-x={x} data-y={y} key={idx}>
<Styled.StyledKey objectKey>
{JSON.stringify(val[0]).replaceAll('"', "")}:{" "}
</Styled.StyledKey>
@@ -42,10 +30,7 @@ const ObjectNode: React.FC<CustomNodeProps> = ({ node, x, y }) => {
};
function propsAreEqual(prev: CustomNodeProps, next: CustomNodeProps) {
return (
String(prev.node.text) === String(next.node.text) &&
prev.node.width === next.node.width
);
return String(prev.node.text) === String(next.node.text) && prev.node.width === next.node.width;
}
export default React.memo(ObjectNode, propsAreEqual);
+48 -40
View File
@@ -2,9 +2,8 @@ import React from "react";
import { MdLink, MdLinkOff } from "react-icons/md";
// import { useInViewport } from "react-in-viewport";
import { CustomNodeProps } from "src/components/CustomNode";
import useConfig from "src/hooks/store/useConfig";
import useGraph from "src/hooks/store/useGraph";
import useStored from "src/hooks/store/useStored";
import useGraph from "src/store/useGraph";
import useStored from "src/store/useStored";
import styled from "styled-components";
import * as Styled from "./styles";
@@ -28,27 +27,34 @@ const StyledExpand = styled.button`
const StyledTextNodeWrapper = styled.div<{ hasCollapse: boolean }>`
display: flex;
justify-content: ${({ hasCollapse }) =>
hasCollapse ? "space-between" : "center"};
justify-content: ${({ hasCollapse }) => (hasCollapse ? "space-between" : "center")};
align-items: center;
height: 100%;
width: 100%;
`;
const TextNode: React.FC<CustomNodeProps> = ({
node,
x,
y,
hasCollapse = false,
}) => {
const StyledImageWrapper = styled.div`
padding: 5px;
`;
const StyledImage = styled.img`
border-radius: 2px;
object-fit: contain;
background: ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
`;
const TextNode: React.FC<CustomNodeProps> = ({ node, x, y, hasCollapse = false }) => {
const { id, text, width, height, data } = node;
const ref = React.useRef(null);
const hideCollapse = useStored(state => state.hideCollapse);
const hideChildrenCount = useStored(state => state.hideChildrenCount);
const childrenCount = useStored(state => state.childrenCount);
const imagePreview = useStored(state => state.imagePreview);
const expandNodes = useGraph(state => state.expandNodes);
const collapseNodes = useGraph(state => state.collapseNodes);
const isExpanded = useGraph(state => state.collapsedParents.includes(id));
const performanceMode = useConfig(state => state.performanceMode);
const performanceMode = useGraph(state => state.performanceMode);
const isImage =
!Array.isArray(text) && /(https?:\/\/.*\.(?:png|jpg|gif))/i.test(text) && imagePreview;
// const { inViewport } = useInViewport(ref);
const handleExpand = (e: React.MouseEvent<HTMLButtonElement>) => {
@@ -64,36 +70,38 @@ const TextNode: React.FC<CustomNodeProps> = ({
height={height}
x={0}
y={0}
hideCollapse={hideCollapse}
hasCollapse={data.isParent && hasCollapse}
hasCollapse={data.parent && hasCollapse}
ref={ref}
>
<StyledTextNodeWrapper hasCollapse={data.isParent && !hideCollapse}>
{(!performanceMode || inViewport) && (
<Styled.StyledKey
data-x={x}
data-y={y}
data-key={JSON.stringify(text)}
parent={data.isParent}
>
<Styled.StyledLinkItUrl>
{JSON.stringify(text).replaceAll('"', "")}
</Styled.StyledLinkItUrl>
</Styled.StyledKey>
)}
{isImage ? (
<StyledImageWrapper>
<StyledImage src={text} width="70" height="70" />
</StyledImageWrapper>
) : (
<StyledTextNodeWrapper hasCollapse={data.parent && hideCollapse}>
{(!performanceMode || inViewport) && (
<Styled.StyledKey
data-x={x}
data-y={y}
data-key={JSON.stringify(text)}
parent={data.parent}
>
<Styled.StyledLinkItUrl>
{JSON.stringify(text).replaceAll('"', "")}
</Styled.StyledLinkItUrl>
</Styled.StyledKey>
)}
{data.parent && data.childrenCount > 0 && childrenCount && (
<Styled.StyledChildrenCount>({data.childrenCount})</Styled.StyledChildrenCount>
)}
{data.isParent && data.childrenCount > 0 && !hideChildrenCount && (
<Styled.StyledChildrenCount>
({data.childrenCount})
</Styled.StyledChildrenCount>
)}
{inViewport && data.isParent && hasCollapse && !hideCollapse && (
<StyledExpand onClick={handleExpand}>
{isExpanded ? <MdLinkOff size={18} /> : <MdLink size={18} />}
</StyledExpand>
)}
</StyledTextNodeWrapper>
{inViewport && data.parent && hasCollapse && hideCollapse && (
<StyledExpand onClick={handleExpand}>
{isExpanded ? <MdLinkOff size={18} /> : <MdLink size={18} />}
</StyledExpand>
)}
</StyledTextNodeWrapper>
)}
</Styled.StyledForeignObject>
);
};
+1 -6
View File
@@ -28,12 +28,7 @@ export const CustomNode = (nodeProps: NodeProps) => {
}
return (
<TextNode
node={node as NodeData}
hasCollapse={data.childrenCount > 0}
x={x}
y={y}
/>
<TextNode node={node as NodeData} hasCollapse={data.childrenCount > 0} x={x} y={y} />
);
}}
</Node>
+16 -13
View File
@@ -2,9 +2,11 @@ import { LinkItUrl } from "react-linkify-it";
import styled, { DefaultTheme } from "styled-components";
function getTypeColor(value: string, theme: DefaultTheme) {
if (!Number.isNaN(+value)) return "#FD0079";
if (value === "true") return theme.TEXT_POSITIVE;
if (value === "false") return theme.TEXT_DANGER;
if (!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;
return theme.NODE_COLORS.NODE_VALUE;
}
export const StyledLinkItUrl = styled(LinkItUrl)`
@@ -14,13 +16,12 @@ export const StyledLinkItUrl = styled(LinkItUrl)`
export const StyledForeignObject = styled.foreignObject<{
hasCollapse?: boolean;
hideCollapse?: boolean;
isObject?: boolean;
}>`
text-align: ${({ isObject }) => !isObject && "center"};
font-size: 12px;
overflow: hidden;
color: ${({ theme }) => theme.TEXT_NORMAL};
color: ${({ theme }) => theme.NODE_COLORS.TEXT};
pointer-events: none;
padding: ${({ isObject }) => isObject && "10px"};
@@ -51,22 +52,24 @@ export const StyledForeignObject = styled.foreignObject<{
}
`;
function getKeyColor(theme: DefaultTheme, parent: boolean, objectKey: boolean) {
if (parent) return theme.NODE_KEY;
if (objectKey) return theme.OBJECT_KEY;
return theme.TEXT_POSITIVE;
function getKeyColor(theme: DefaultTheme, parent: "array" | "object" | false, objectKey: boolean) {
if (parent) {
if (parent === "array") return theme.NODE_COLORS.PARENT_ARR;
return theme.NODE_COLORS.PARENT_OBJ;
}
if (objectKey) return theme.NODE_COLORS.NODE_KEY;
return theme.NODE_COLORS.TEXT;
}
export const StyledKey = styled.span<{
objectKey?: boolean;
parent?: boolean;
parent?: "array" | "object" | false;
value?: string;
}>`
display: inline;
flex: 1;
font-weight: 500;
color: ${({ theme, objectKey = false, parent = false }) =>
getKeyColor(theme, parent, objectKey)};
color: ${({ theme, objectKey = false, parent = false }) => getKeyColor(theme, parent, objectKey)};
font-size: ${({ parent }) => parent && "14px"};
overflow: hidden;
text-overflow: ellipsis;
@@ -90,7 +93,7 @@ export const StyledRow = styled.span.attrs<{
`;
export const StyledChildrenCount = styled.span`
color: ${({ theme }) => theme.TEXT_POSITIVE};
color: ${({ theme }) => theme.NODE_COLORS.CHILD_COUNT};
padding: 10px;
margin-left: -15px;
`;
+4 -1
View File
@@ -1,5 +1,6 @@
import React from "react";
import { MdReportGmailerrorred, MdOutlineCheckCircleOutline } from "react-icons/md";
import useJson from "src/store/useJson";
import styled from "styled-components";
const StyledErrorWrapper = styled.div`
@@ -40,7 +41,9 @@ const StyledError = styled.pre`
white-space: pre-line;
`;
export const ErrorContainer = ({ hasError }: { hasError: boolean }) => {
export const ErrorContainer = () => {
const hasError = useJson(state => state.hasError);
return (
<StyledErrorWrapper>
<StyledErrorExpand error={hasError}>
+84
View File
@@ -0,0 +1,84 @@
import Link from "next/link";
import { FaGithub, FaLinkedin, FaTwitter } from "react-icons/fa";
import styled from "styled-components";
import pkg from "../../../package.json";
export const StyledFooter = styled.footer`
display: flex;
flex-direction: row;
justify-content: space-between;
width: 80%;
margin: 0 auto;
padding: 30px 3%;
border-top: 1px solid #b4b4b4;
opacity: 0.7;
`;
export const StyledFooterText = styled.p`
display: flex;
flex-direction: column;
gap: 20px;
color: #b4b4b4;
`;
export const StyledNavLink = styled.a`
display: flex;
justify-content: center;
align-items: center;
font-size: 1rem;
cursor: pointer;
transition: color 0.2s;
&:hover {
font-weight: 500;
color: ${({ theme }) => theme.ORANGE};
}
`;
export const StyledIconLinks = styled.div`
display: flex;
gap: 20px;
`;
export const Footer = () => (
<StyledFooter>
<StyledFooterText>
<Link href="/">
<a>
<img width="120" src="assets/icon.png" alt="icon" loading="lazy" />
</a>
</Link>
<span>
© {new Date().getFullYear()} JSON Crack - {pkg.version}
</span>
</StyledFooterText>
<StyledIconLinks>
<StyledNavLink
href="https://github.com/AykutSarac/jsoncrack.com"
rel="external"
target="_blank"
aria-label="github"
>
<FaGithub size={26} />
</StyledNavLink>
<StyledNavLink
href="https://www.linkedin.com/in/aykutsarac/"
rel="me"
target="_blank"
aria-label="linkedin"
>
<FaLinkedin size={26} />
</StyledNavLink>
<StyledNavLink
href="https://twitter.com/jsoncrack"
rel="me"
target="_blank"
aria-label="twitter"
>
<FaTwitter size={26} />
</StyledNavLink>
</StyledIconLinks>
</StyledFooter>
);
+5 -10
View File
@@ -26,17 +26,12 @@ const StyledInfo = styled.p`
export const ErrorView = () => (
<StyledErrorView>
<img
src="/assets/undraw_qa_engineers_dg-5-p.svg"
width="200"
height="200"
alt="oops"
/>
<StyledTitle>JSON Crack unable to handle this file!</StyledTitle>
<img src="/assets/undraw_qa_engineers_dg-5-p.svg" width="200" height="200" alt="oops" />
<StyledTitle>JSON Crack is unable to handle this file!</StyledTitle>
<StyledInfo>
We apologize for the problem you encountered, we are doing our best as Open
Source community to improve our service. JSON Crack is currently unable to
handle such a huge file.
We apologize for the problem you encountered. We are doing our best as an Open Source
community to improve our service. Unfortunately, JSON Crack is currently unable to handle such
a large file.
</StyledInfo>
</StyledErrorView>
);
+28 -42
View File
@@ -1,18 +1,13 @@
import React from "react";
import {
ReactZoomPanPinchRef,
TransformComponent,
TransformWrapper,
} from "react-zoom-pan-pinch";
import { ReactZoomPanPinchRef, TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
import { Canvas, Edge, ElkRoot } from "reaflow";
import { CustomNode } from "src/components/CustomNode";
import useConfig from "src/hooks/store/useConfig";
import useGraph from "src/hooks/store/useGraph";
import useGraph from "src/store/useGraph";
import styled from "styled-components";
import { Loading } from "../Loading";
import { ErrorView } from "./ErrorView";
interface LayoutProps {
interface GraphProps {
isWidget?: boolean;
openModal: () => void;
setSelectedNode: (node: [string, string][]) => void;
@@ -31,7 +26,8 @@ const StyledEditorWrapper = styled.div<{ isWidget: boolean }>`
cursor: move;
}
.dragging {
.dragging,
.dragging button {
pointer-events: none;
}
@@ -40,17 +36,13 @@ const StyledEditorWrapper = styled.div<{ isWidget: boolean }>`
}
`;
const GraphComponent = ({
isWidget = false,
openModal,
setSelectedNode,
}: LayoutProps) => {
const [minScale, setMinScale] = React.useState(0.4);
const GraphComponent = ({ isWidget = false, openModal, setSelectedNode }: GraphProps) => {
const setLoading = useGraph(state => state.setLoading);
const setConfig = useConfig(state => state.setConfig);
const centerView = useConfig(state => state.centerView);
const setZoomPanPinch = useGraph(state => state.setZoomPanPinch);
const centerView = useGraph(state => state.centerView);
const loading = useGraph(state => state.loading);
const layout = useConfig(state => state.layout);
const direction = useGraph(state => state.direction);
const nodes = useGraph(state => state.nodes);
const edges = useGraph(state => state.edges);
@@ -69,31 +61,29 @@ const GraphComponent = ({
const onInit = React.useCallback(
(ref: ReactZoomPanPinchRef) => {
setConfig("zoomPanPinch", ref);
setZoomPanPinch(ref);
},
[setConfig]
[setZoomPanPinch]
);
const onLayoutChange = React.useCallback(
(layout: ElkRoot) => {
if (layout.width && layout.height) {
const areaSize = layout.width * layout.height;
const changeRatio = Math.abs(
(areaSize * 100) / (size.width * size.height) - 100
);
const changeRatio = Math.abs((areaSize * 100) / (size.width * size.height) - 100);
const MIN_SCALE = Math.round((450_000 / areaSize) * 100) / 100;
const scale = MIN_SCALE > 2 ? 1 : MIN_SCALE <= 0 ? 0.1 : MIN_SCALE;
setLoading(true);
setMinScale(scale);
setSize({ width: layout.width + 400, height: layout.height + 400 });
setSize({
width: (layout.width as number) + 400,
height: (layout.height as number) + 400,
});
requestAnimationFrame(() => {
setTimeout(() => {
setLoading(false);
setTimeout(() => (changeRatio > 100 || isWidget) && centerView(), 0);
}, 0);
setTimeout(() => {
if (changeRatio > 70 || isWidget) centerView();
});
});
});
}
},
@@ -108,20 +98,18 @@ const GraphComponent = ({
if (nodes.length > 8_000) return <ErrorView />;
return (
<StyledEditorWrapper isWidget={isWidget}>
{loading && <Loading message="Painting graph..." />}
<StyledEditorWrapper isWidget={isWidget} onContextMenu={e => e.preventDefault()}>
<Loading message="Painting graph..." loading={loading} />
<TransformWrapper
maxScale={2}
minScale={minScale}
minScale={0.05}
initialScale={0.4}
wheel={{ step: 0.08 }}
zoomAnimation={{ animationType: "linear" }}
doubleClick={{ disabled: true }}
onInit={onInit}
onPanning={ref => ref.instance.wrapperComponent?.classList.add("dragging")}
onPanningStop={ref =>
ref.instance.wrapperComponent?.classList.remove("dragging")
}
onPanningStop={ref => ref.instance.wrapperComponent?.classList.remove("dragging")}
>
<TransformComponent
wrapperStyle={{
@@ -137,20 +125,18 @@ const GraphComponent = ({
edges={edges}
maxWidth={size.width}
maxHeight={size.height}
direction={layout}
direction={direction}
onLayoutChange={onLayoutChange}
onCanvasClick={onCanvasClick}
key={layout}
zoomable={false}
animated={false}
readonly={true}
dragEdge={null}
dragNode={null}
fit={true}
key={direction}
node={props => <CustomNode {...props} onClick={handleNodeClick} />}
edge={props => (
<Edge {...props} containerClassName={`edge-${props.id}`} />
)}
edge={props => <Edge {...props} containerClassName={`edge-${props.id}`} />}
/>
</TransformComponent>
</TransformWrapper>
+14 -11
View File
@@ -2,6 +2,7 @@ import React from "react";
import styled, { keyframes } from "styled-components";
interface LoadingProps {
loading?: boolean;
message?: string;
}
@@ -32,7 +33,7 @@ const StyledLoading = styled.div`
`;
const StyledLogo = styled.h2`
font-weight: 600;
font-weight: 800;
font-size: 56px;
pointer-events: none;
margin-bottom: 10px;
@@ -48,13 +49,15 @@ const StyledMessage = styled.div`
font-weight: 500;
`;
export const Loading: React.FC<LoadingProps> = ({ message }) => (
<StyledLoading>
<StyledLogo>
<StyledText>JSON</StyledText> Crack
</StyledLogo>
<StyledMessage>
{message ?? "Preparing the environment for you..."}
</StyledMessage>
</StyledLoading>
);
export const Loading: React.FC<LoadingProps> = ({ loading = true, message }) => {
if (!loading) return null;
return (
<StyledLoading>
<StyledLogo>
<StyledText>JSON</StyledText> Crack
</StyledLogo>
<StyledMessage>{message ?? "Preparing the environment for you..."}</StyledMessage>
</StyledLoading>
);
};
+5 -3
View File
@@ -17,7 +17,8 @@ type ModalTypes = {
export interface ModalProps {
visible: boolean;
setVisible: React.Dispatch<React.SetStateAction<boolean>>;
setVisible: React.Dispatch<React.SetStateAction<boolean>> | ((visible: boolean) => void);
size?: "sm" | "md" | "lg";
}
const Header: ReactComponent = ({ children }) => {
@@ -51,10 +52,11 @@ const Modal: React.FC<React.PropsWithChildren<ModalProps>> & ModalTypes = ({
children,
visible,
setVisible,
size = "sm",
}) => {
const onClick = (e: React.SyntheticEvent<HTMLDivElement>) => {
if (e.currentTarget === e.target) {
setVisible(v => !v);
setVisible(false);
}
};
@@ -62,7 +64,7 @@ const Modal: React.FC<React.PropsWithChildren<ModalProps>> & ModalTypes = ({
return (
<Styled.ModalWrapper onClick={onClick}>
<Styled.ModalInnerWrapper>{children}</Styled.ModalInnerWrapper>
<Styled.ModalInnerWrapper size={size}>{children}</Styled.ModalInnerWrapper>
</Styled.ModalWrapper>
);
};
+8 -4
View File
@@ -22,9 +22,9 @@ export const ModalWrapper = styled.div`
}
`;
export const ModalInnerWrapper = styled.div`
export const ModalInnerWrapper = styled.div<{ size: "sm" | "md" | "lg" }>`
min-width: 440px;
max-width: 490px;
max-width: ${({ size }) => (size === "sm" ? "490px" : size === "md" ? "50%" : "90%")};
width: fit-content;
animation: ${appearAnimation} 220ms ease-in-out;
line-height: 20px;
@@ -36,9 +36,12 @@ export const ModalInnerWrapper = styled.div`
`;
export const Title = styled.h2`
display: flex;
align-items: center;
gap: 5px;
color: ${({ theme }) => theme.INTERACTIVE_ACTIVE};
font-size: 20px !important;
margin: 0;
margin: 0 !important;
`;
export const HeaderWrapper = styled.div`
@@ -52,13 +55,14 @@ export const ContentWrapper = styled.div`
background: ${({ theme }) => theme.MODAL_BACKGROUND};
padding: 16px;
overflow: hidden auto;
max-height: 500px;
`;
export const ControlsWrapper = styled.div`
display: flex;
flex-direction: row-reverse;
background: ${({ theme }) => theme.BACKGROUND_SECONDARY};
padding: 16px;
padding: 12px;
border-radius: 0 0 5px 5px;
gap: 10px;
`;
+56 -43
View File
@@ -1,11 +1,9 @@
import React from "react";
import Editor, { loader, Monaco } from "@monaco-editor/react";
import { parse } from "jsonc-parser";
import debounce from "lodash.debounce";
import { Loading } from "src/components/Loading";
import useConfig from "src/hooks/store/useConfig";
import useGraph from "src/hooks/store/useGraph";
import useStored from "src/hooks/store/useStored";
import { parser } from "src/utils/jsonParser";
import useJson from "src/store/useJson";
import useStored from "src/store/useStored";
import styled from "styled-components";
loader.config({
@@ -28,63 +26,78 @@ const StyledWrapper = styled.div`
grid-template-rows: minmax(0, 1fr);
`;
function handleEditorWillMount(monaco: Monaco) {
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
allowComments: true,
comments: "ignore",
});
}
export const MonacoEditor = () => {
const json = useJson(state => state.json);
const setJson = useJson(state => state.setJson);
const setError = useJson(state => state.setError);
const [loaded, setLoaded] = React.useState(false);
const [value, setValue] = React.useState<string | undefined>(json);
export const MonacoEditor = ({
setHasError,
}: {
setHasError: (value: boolean) => void;
}) => {
const json = useConfig(state => state.json);
const expand = useConfig(state => state.expand);
const setJson = useConfig(state => state.setJson);
const setGraphValue = useGraph(state => state.setGraphValue);
const hasError = useJson(state => state.hasError);
const getHasChanges = useJson(state => state.getHasChanges);
const lightmode = useStored(state => (state.lightmode ? "light" : "vs-dark"));
const [value, setValue] = React.useState<string | undefined>("");
const handleEditorWillMount = React.useCallback(
(monaco: Monaco) => {
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
allowComments: true,
comments: "ignore",
});
monaco.editor.onDidChangeMarkers(([uri]) => {
const markers = monaco.editor.getModelMarkers({ resource: uri });
setError(!!markers.length);
});
},
[setError]
);
const debouncedSetJson = React.useMemo(
() =>
debounce(value => {
if (hasError) return;
setJson(value || "[]");
}, 1200),
[hasError, setJson]
);
React.useEffect(() => {
const { nodes, edges } = parser(json, expand);
if ((value || !hasError) && loaded) debouncedSetJson(value);
setLoaded(true);
setGraphValue("loading", true);
setGraphValue("nodes", nodes);
setGraphValue("edges", edges);
setValue(json);
}, [expand, json, setGraphValue]);
return () => debouncedSetJson.cancel();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedSetJson, hasError, value]);
React.useEffect(() => {
const formatTimer = setTimeout(() => {
if (!value) {
setHasError(false);
return setJson("{}");
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;
}
};
const errors = [];
const parsedJSON = JSON.stringify(parse(value, errors), null, 2);
if (errors.length) return setHasError(true);
window.addEventListener("beforeunload", beforeunload);
setJson(parsedJSON);
setHasError(false);
}, 1200);
return () => clearTimeout(formatTimer);
}, [value, setJson, setHasError]);
return () => {
window.removeEventListener("beforeunload", beforeunload);
};
}, [getHasChanges]);
return (
<StyledWrapper>
<Editor
height="100%"
defaultLanguage="json"
value={value}
value={json}
theme={lightmode}
options={editorOptions}
onChange={setValue}
loading={<Loading message="Loading Editor..." />}
beforeMount={handleEditorWillMount}
defaultLanguage="json"
height="100%"
/>
</StyledWrapper>
);
+1 -5
View File
@@ -76,11 +76,7 @@ export const SearchInput: React.FC = () => {
placeholder="Search Node"
/>
<StyledSearchButton type="reset" aria-label="search" onClick={handleClear}>
{content.value ? (
<IoCloseSharp size={18} />
) : (
<AiOutlineSearch size={18} />
)}
{content.value ? <IoCloseSharp size={18} /> : <AiOutlineSearch size={18} />}
</StyledSearchButton>
</StyledForm>
</StyledInputWrapper>
+134 -144
View File
@@ -1,36 +1,23 @@
import React from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import toast from "react-hot-toast";
import {
AiOutlineDelete,
AiFillGithub,
AiOutlineTwitter,
AiOutlineSave,
AiOutlineFileAdd,
AiOutlineLink,
AiOutlineEdit,
} from "react-icons/ai";
import {
CgArrowsMergeAltH,
CgArrowsMergeAltV,
CgArrowsShrinkH,
CgArrowsShrinkV,
} from "react-icons/cg";
import { AiOutlineDelete, AiOutlineSave, AiOutlineFileAdd, AiOutlineEdit } from "react-icons/ai";
import { CgArrowsMergeAltH, CgArrowsShrinkH } from "react-icons/cg";
import { FiDownload } from "react-icons/fi";
import { HiHeart } from "react-icons/hi";
import { MdCenterFocusWeak } from "react-icons/md";
import { TiFlowMerge } from "react-icons/ti";
import {
VscAccount,
VscCloud,
VscCollapseAll,
VscExpandAll,
VscSettingsGear,
} from "react-icons/vsc";
import { Tooltip } from "src/components/Tooltip";
import { ClearModal } from "src/containers/Modals/ClearModal";
import { DownloadModal } from "src/containers/Modals/DownloadModal";
import { ImportModal } from "src/containers/Modals/ImportModal";
import { ShareModal } from "src/containers/Modals/ShareModal";
import useConfig from "src/hooks/store/useConfig";
import useGraph from "src/hooks/store/useGraph";
import { getNextLayout } from "src/utils/getNextLayout";
import useGraph from "src/store/useGraph";
import useJson from "src/store/useJson";
import useModal from "src/store/useModal";
import { getNextDirection } from "src/utils/getNextDirection";
import styled from "styled-components";
import shallow from "zustand/shallow";
const StyledSidebar = styled.div`
display: flex;
@@ -53,7 +40,7 @@ const StyledElement = styled.button`
display: flex;
justify-content: center;
text-align: center;
font-size: 26px;
font-size: 24px;
font-weight: 600;
width: fit-content;
color: ${({ theme }) => theme.SIDEBAR_ICONS};
@@ -83,8 +70,7 @@ const StyledElement = styled.button`
`;
const StyledText = styled.span<{ secondary?: boolean }>`
color: ${({ theme, secondary }) =>
secondary ? theme.INTERACTIVE_HOVER : theme.ORANGE};
color: ${({ theme, secondary }) => (secondary ? theme.INTERACTIVE_HOVER : theme.ORANGE)};
`;
const StyledFlowIcon = styled(TiFlowMerge)<{ rotate: number }>`
@@ -138,29 +124,41 @@ const StyledLogo = styled.a`
}
`;
function rotateLayout(layout: "LEFT" | "RIGHT" | "DOWN" | "UP") {
if (layout === "LEFT") return 90;
if (layout === "UP") return 180;
if (layout === "RIGHT") return 270;
function rotateLayout(direction: "LEFT" | "RIGHT" | "DOWN" | "UP") {
if (direction === "LEFT") return 90;
if (direction === "UP") return 180;
if (direction === "RIGHT") return 270;
return 360;
}
export const Sidebar: React.FC = () => {
const getJson = useConfig(state => state.getJson);
const setConfig = useConfig(state => state.setConfig);
const centerView = useConfig(state => state.centerView);
const [uploadVisible, setUploadVisible] = React.useState(false);
const [clearVisible, setClearVisible] = React.useState(false);
const [shareVisible, setShareVisible] = React.useState(false);
const [isDownloadVisible, setDownloadVisible] = React.useState(false);
const { push } = useRouter();
const [expand, layout, hideEditor] = useConfig(
state => [state.expand, state.layout, state.hideEditor],
shallow
const SidebarButton: React.FC<{
onClick: () => void;
deviceDisplay?: "desktop" | "mobile";
title: string;
component: React.ReactNode;
}> = ({ onClick, deviceDisplay, title, component }) => {
return (
<Tooltip className={deviceDisplay} title={title}>
<StyledElement onClick={onClick}>{component}</StyledElement>
</Tooltip>
);
};
const { collapseGraph, expandGraph, graphCollapsed } = useGraph();
export const Sidebar: React.FC = () => {
const setVisible = useModal(state => state.setVisible);
const setDirection = useGraph(state => state.setDirection);
const getJson = useJson(state => state.getJson);
const collapseGraph = useGraph(state => state.collapseGraph);
const expandGraph = useGraph(state => state.expandGraph);
const centerView = useGraph(state => state.centerView);
const toggleFold = useGraph(state => state.toggleFold);
const toggleFullscreen = useGraph(state => state.toggleFullscreen);
const direction = useGraph(state => state.direction);
const foldNodes = useGraph(state => state.foldNodes);
const fullscreen = useGraph(state => state.fullscreen);
const graphCollapsed = useGraph(state => state.graphCollapsed);
const handleSave = () => {
const a = document.createElement("a");
@@ -171,118 +169,110 @@ export const Sidebar: React.FC = () => {
a.click();
};
const toggleExpandShrink = () => {
setConfig("expand", !expand);
toast(`${expand ? "Shrunk" : "Expanded"} nodes.`);
const toggleFoldNodes = () => {
toggleFold(!foldNodes);
toast(`${foldNodes ? "Unfolded" : "Folded"} nodes`);
};
const toggleLayout = () => {
const nextLayout = getNextLayout(layout);
setConfig("layout", nextLayout);
const toggleDirection = () => {
const nextDirection = getNextDirection(direction);
setDirection(nextDirection);
};
const toggleExpandCollapseGraph = () => {
if (graphCollapsed) {
expandGraph();
} else {
collapseGraph();
}
if (graphCollapsed) expandGraph();
else collapseGraph();
toast(`${graphCollapsed ? "Expanded" : "Collapsed"} graph.`);
};
return (
<StyledSidebar>
<StyledTopWrapper>
<Link passHref href="/">
<StyledElement as={StyledLogo}>
<StyledText>J</StyledText>
<StyledText secondary>C</StyledText>
</StyledElement>
</Link>
<Tooltip className="mobile" title="Edit JSON">
<StyledElement onClick={() => setConfig("hideEditor", !hideEditor)}>
<AiOutlineEdit />
</StyledElement>
</Tooltip>
<Tooltip title="Import File">
<StyledElement onClick={() => setUploadVisible(true)}>
<AiOutlineFileAdd />
</StyledElement>
</Tooltip>
<Tooltip title="Rotate Layout">
<StyledElement onClick={toggleLayout}>
<StyledFlowIcon rotate={rotateLayout(layout)} />
</StyledElement>
</Tooltip>
<Tooltip className="mobile" title="Center View">
<StyledElement onClick={centerView}>
<MdCenterFocusWeak />
</StyledElement>
</Tooltip>
<Tooltip
className="desktop"
title={expand ? "Shrink Nodes" : "Expand Nodes"}
>
<StyledElement title="Toggle Expand/Shrink" onClick={toggleExpandShrink}>
{expand ? <CgArrowsMergeAltH /> : <CgArrowsShrinkH />}
</StyledElement>
</Tooltip>
<Tooltip
className="desktop"
<StyledElement href="/" as={StyledLogo}>
<StyledText>J</StyledText>
<StyledText secondary>C</StyledText>
</StyledElement>
<SidebarButton
title="Edit JSON"
deviceDisplay="mobile"
onClick={() => toggleFullscreen(!fullscreen)}
component={<AiOutlineEdit />}
/>
<SidebarButton
title="Import File"
onClick={() => setVisible("import")(true)}
component={<AiOutlineFileAdd />}
/>
<SidebarButton
title="Rotate Layout"
onClick={toggleDirection}
component={<StyledFlowIcon rotate={rotateLayout(direction)} />}
/>
<SidebarButton
title="Center View"
deviceDisplay="mobile"
onClick={centerView}
component={<MdCenterFocusWeak />}
/>
<SidebarButton
title={foldNodes ? "Unfold Nodes" : "Fold Nodes"}
deviceDisplay="desktop"
onClick={toggleFoldNodes}
component={foldNodes ? <CgArrowsShrinkH /> : <CgArrowsMergeAltH />}
/>
<SidebarButton
title={graphCollapsed ? "Expand Graph" : "Collapse Graph"}
>
<StyledElement onClick={toggleExpandCollapseGraph}>
{graphCollapsed ? <CgArrowsShrinkV /> : <CgArrowsMergeAltV />}
</StyledElement>
</Tooltip>
<Tooltip className="desktop" title="Save JSON">
<StyledElement onClick={handleSave}>
<AiOutlineSave />
</StyledElement>
</Tooltip>
<Tooltip className="mobile" title="Download Image">
<StyledElement onClick={() => setDownloadVisible(true)}>
<FiDownload />
</StyledElement>
</Tooltip>
<Tooltip title="Clear JSON">
<StyledElement onClick={() => setClearVisible(true)}>
<AiOutlineDelete />
</StyledElement>
</Tooltip>
<Tooltip className="desktop" title="Share">
<StyledElement onClick={() => setShareVisible(true)}>
<AiOutlineLink />
</StyledElement>
</Tooltip>
deviceDisplay="desktop"
onClick={toggleExpandCollapseGraph}
component={graphCollapsed ? <VscExpandAll /> : <VscCollapseAll />}
/>
<SidebarButton
title="Download JSON"
deviceDisplay="desktop"
onClick={handleSave}
component={<AiOutlineSave />}
/>
<SidebarButton
title="Download Image"
deviceDisplay="mobile"
onClick={() => setVisible("download")(true)}
component={<FiDownload />}
/>
<SidebarButton
title="Delete JSON"
onClick={() => setVisible("clear")(true)}
component={<AiOutlineDelete />}
/>
<SidebarButton
title="View Cloud"
deviceDisplay="desktop"
onClick={() => setVisible("cloud")(true)}
component={<VscCloud />}
/>
</StyledTopWrapper>
<StyledBottomWrapper>
<StyledElement>
<Link href="https://twitter.com/jsoncrack">
<a aria-label="Twitter" rel="me" target="_blank">
<AiOutlineTwitter />
</a>
</Link>
</StyledElement>
<StyledElement>
<Link href="https://github.com/AykutSarac/jsoncrack.com">
<a aria-label="GitHub" rel="me" target="_blank">
<AiFillGithub />
</a>
</Link>
</StyledElement>
<StyledElement>
<Link href="https://github.com/sponsors/AykutSarac">
<a aria-label="GitHub Sponsors" rel="me" target="_blank">
<HiHeart />
</a>
</Link>
</StyledElement>
<SidebarButton
title="Account"
onClick={() => setVisible("account")(true)}
component={<VscAccount />}
/>
<SidebarButton
title="Settings"
onClick={() => setVisible("settings")(true)}
component={<VscSettingsGear />}
/>
</StyledBottomWrapper>
<ImportModal visible={uploadVisible} setVisible={setUploadVisible} />
<ClearModal visible={clearVisible} setVisible={setClearVisible} />
<ShareModal visible={shareVisible} setVisible={setShareVisible} />
<DownloadModal visible={isDownloadVisible} setVisible={setDownloadVisible} />
</StyledSidebar>
);
};
+28
View File
@@ -0,0 +1,28 @@
import React from "react";
import { CgSpinner } from "react-icons/cg";
import styled, { keyframes } from "styled-components";
const rotateAnimation = keyframes`
to { transform: rotate(360deg); }
`;
const StyledSpinnerWrapper = styled.div`
display: flex;
align-items: center;
padding: 25px;
justify-content: center;
width: 100%;
height: 100%;
svg {
animation: ${rotateAnimation} 1s linear infinite;
}
`;
export const Spinner = () => {
return (
<StyledSpinnerWrapper>
<CgSpinner size={40} />
</StyledSpinnerWrapper>
);
};
+4 -11
View File
@@ -1,5 +1,5 @@
import React from "react";
import useStored from "src/hooks/store/useStored";
import useStored from "src/store/useStored";
import styled from "styled-components";
async function getSponsors() {
@@ -24,7 +24,7 @@ async function getSponsors() {
const StyledSponsorsWrapper = styled.ul`
display: flex;
width: 100%;
width: 70%;
margin: 0;
padding: 0;
list-style: none;
@@ -60,8 +60,7 @@ const StyledSponsor = styled.li<{ handle: string }>`
transform: translateY(-110%);
border-width: 5px;
border-style: solid;
border-color: ${({ theme }) => theme.BACKGROUND_PRIMARY} transparent
transparent transparent;
border-color: ${({ theme }) => theme.BACKGROUND_PRIMARY} transparent transparent transparent;
}
}
@@ -86,13 +85,7 @@ export const Sponsors = () => {
{sponsors.users.map(user => (
<StyledSponsor handle={user.handle} key={user.handle}>
<a href={user.profile} target="_blank" rel="noreferrer">
<img
src={user.avatar}
alt={user.handle}
width="40"
height="40"
loading="lazy"
/>
<img src={user.avatar} alt={user.handle} width="40" height="40" loading="lazy" />
</a>
</StyledSponsor>
))}
+4 -9
View File
@@ -26,9 +26,10 @@ const StyledSupportButton = styled.a`
transition: all 0.5s;
overflow: hidden;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.07), 0 2px 4px rgba(0, 0, 0, 0.07),
0 4px 8px rgba(0, 0, 0, 0.07), 0 8px 16px rgba(0, 0, 0, 0.07),
0 16px 32px rgba(0, 0, 0, 0.07), 0 32px 64px rgba(0, 0, 0, 0.07);
0 4px 8px rgba(0, 0, 0, 0.07), 0 8px 16px rgba(0, 0, 0, 0.07), 0 16px 32px rgba(0, 0, 0, 0.07),
0 32px 64px rgba(0, 0, 0, 0.07);
opacity: 0.7;
box-sizing: content-box !important;
&:hover {
width: 180px;
@@ -43,14 +44,8 @@ const StyledSupportButton = styled.a`
`;
export const SupportButton = () => {
if (location.pathname.includes("widget")) return null;
return (
<StyledSupportButton
href="https://github.com/sponsors/AykutSarac"
target="_blank"
rel="me"
>
<StyledSupportButton href="https://github.com/sponsors/AykutSarac" target="_blank" rel="me">
<StyledText>Support JSON Crack</StyledText>
<HiHeart size={25} />
</StyledSupportButton>
+22 -30
View File
@@ -5,14 +5,9 @@ interface TooltipProps extends React.ComponentPropsWithoutRef<"div"> {
title?: string;
}
const StyledTooltipWrapper = styled.div`
position: relative;
width: fit-content;
height: 100%;
`;
const StyledTooltip = styled.div<{ visible: boolean }>`
const StyledTooltip = styled.div`
position: absolute;
display: none;
top: 0;
right: 0;
transform: translate(calc(100% + 15px), 25%);
@@ -20,15 +15,15 @@ const StyledTooltip = styled.div<{ visible: boolean }>`
background: ${({ theme }) => theme.BACKGROUND_PRIMARY};
color: ${({ theme }) => theme.TEXT_NORMAL};
border-radius: 5px;
padding: 4px 8px;
display: ${({ visible }) => (visible ? "initial" : "none")};
padding: 6px 8px;
white-space: nowrap;
font-family: "Mona Sans";
font-size: 16px;
user-select: none;
font-weight: 500;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.07), 0 2px 4px rgba(0, 0, 0, 0.07),
0 4px 8px rgba(0, 0, 0, 0.07), 0 8px 16px rgba(0, 0, 0, 0.07),
0 16px 32px rgba(0, 0, 0, 0.07), 0 32px 64px rgba(0, 0, 0, 0.07);
0 4px 8px rgba(0, 0, 0, 0.07), 0 8px 16px rgba(0, 0, 0, 0.07), 0 16px 32px rgba(0, 0, 0, 0.07),
0 32px 64px rgba(0, 0, 0, 0.07);
&::after {
content: "";
@@ -38,8 +33,7 @@ const StyledTooltip = styled.div<{ visible: boolean }>`
transform: translate(-90%, 50%);
border-width: 8px;
border-style: solid;
border-color: transparent ${({ theme }) => theme.BACKGROUND_PRIMARY} transparent
transparent;
border-color: transparent ${({ theme }) => theme.BACKGROUND_PRIMARY} transparent transparent;
}
@media only screen and (max-width: 768px) {
@@ -47,25 +41,23 @@ const StyledTooltip = styled.div<{ visible: boolean }>`
}
`;
const StyledChildren = styled.div``;
const StyledTooltipWrapper = styled.div`
position: relative;
width: fit-content;
height: 100%;
&:hover ${StyledTooltip} {
display: initial;
}
`;
export const Tooltip: React.FC<React.PropsWithChildren<TooltipProps>> = ({
children,
title,
...props
}) => {
const [visible, setVisible] = React.useState(false);
return (
<StyledTooltipWrapper {...props}>
{title && <StyledTooltip visible={visible}>{title}</StyledTooltip>}
<StyledChildren
onMouseEnter={() => setVisible(true)}
onMouseLeave={() => setVisible(false)}
>
{children}
</StyledChildren>
</StyledTooltipWrapper>
);
};
}) => (
<StyledTooltipWrapper {...props}>
{title && <StyledTooltip>{title}</StyledTooltip>}
<div>{children}</div>
</StyledTooltipWrapper>
);
+26 -11
View File
@@ -1,32 +1,45 @@
import { createGlobalStyle } from "styled-components";
const GlobalStyle = createGlobalStyle`
@font-face {
font-family: 'Mona Sans';
src:
url('assets/Mona-Sans.woff2') format('woff2 supports variations'),
url('assets/Mona-Sans.woff2') format('woff2-variations');
font-weight: 200 900;
font-stretch: 75% 125%;
}
svg {
vertical-align: top;
}
h1, h2, h3, h4, p {
font-family: 'Mona Sans';
}
html, body {
margin: 0;
padding: 0;
box-sizing: border-box;
color: ${({ theme }) => theme.FULL_WHITE};
font-family: 'Catamaran', sans-serif;
font-family: 'Mona Sans';
font-weight: 400;
font-size: 16px;
scroll-behavior: smooth;
height: 100%;
background-color: #000000;
opacity: 1;
background-image: radial-gradient(#414141 0.5px, #000000 0.5px);
background-size: 10px 10px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 800 800'%3E%3Cg fill-opacity='0.3'%3E%3Ccircle fill='%23000000' cx='400' cy='400' r='600'/%3E%3Ccircle fill='%23110718' cx='400' cy='400' r='500'/%3E%3Ccircle fill='%23220e30' cx='400' cy='400' r='400'/%3E%3Ccircle fill='%23331447' cx='400' cy='400' r='300'/%3E%3Ccircle fill='%23441b5f' cx='400' cy='400' r='200'/%3E%3Ccircle fill='%23552277' cx='400' cy='400' r='100'/%3E%3C/g%3E%3C/svg%3E");
background-attachment: fixed;
background-size: cover;
@media only screen and (min-width: 768px) {
background-color: #000000;
opacity: 1;
background-image: radial-gradient(#414141 0.5px, #000000 0.5px);
background-size: 15px 15px;
@media only screen and (max-width: 768px) {
background-position: right;
}
}
* {
-webkit-tap-highlight-color: transparent;
scroll-behavior: smooth;
}
.hide {
@@ -42,6 +55,7 @@ const GlobalStyle = createGlobalStyle`
}
button {
font-family: 'Mona Sans';
border: none;
outline: none;
background: transparent;
@@ -49,6 +63,7 @@ const GlobalStyle = createGlobalStyle`
margin: 0;
padding: 0;
cursor: pointer;
font-weight: 800;
}
#carbonads * {
+49 -6
View File
@@ -1,8 +1,7 @@
import { DefaultTheme } from "styled-components";
const fixedColors = {
CRIMSON: "#DC143C",
BLURPLE: "#5865F2",
PURPLE: "#9036AF",
FULL_WHITE: "#FFFFFF",
BLACK: "#202225",
BLACK_DARK: "#2C2F33",
@@ -18,8 +17,44 @@ const fixedColors = {
TEXT_DANGER: "#db662e",
};
export const darkTheme: DefaultTheme = {
const nodeColors = {
dark: {
NODE_COLORS: {
TEXT: "#35D073",
NODE_KEY: "#59b8ff",
NODE_VALUE: "#DCE5E7",
INTEGER: "#e8c479",
NULL: "#939598",
BOOL: {
FALSE: "#F85C50",
TRUE: "#00DC7D",
},
PARENT_ARR: "#FC9A40",
PARENT_OBJ: "#59b8ff",
CHILD_COUNT: "white",
},
},
light: {
NODE_COLORS: {
TEXT: "#748700",
NODE_KEY: "#761CEA",
NODE_VALUE: "#535353",
INTEGER: "#A771FE",
NULL: "#afafaf",
BOOL: {
FALSE: "#FF0000",
TRUE: "#748700",
},
PARENT_ARR: "#FF6B00",
PARENT_OBJ: "#761CEA",
CHILD_COUNT: "#535353",
},
},
};
export const darkTheme = {
...fixedColors,
...nodeColors.dark,
BLACK_SECONDARY: "#23272A",
SILVER_DARK: "#4D4D4D",
NODE_KEY: "#FAA81A",
@@ -37,10 +72,11 @@ export const darkTheme: DefaultTheme = {
MODAL_BACKGROUND: "#36393E",
TEXT_NORMAL: "#dcddde",
TEXT_POSITIVE: "hsl(139,calc(var(--saturation-factor, 1)*51.6%),52.2%)",
} as const;
};
export const lightTheme: DefaultTheme = {
export const lightTheme = {
...fixedColors,
...nodeColors.light,
BLACK_SECONDARY: "#F2F2F2",
SILVER_DARK: "#CCCCCC",
NODE_KEY: "#DC3790",
@@ -58,4 +94,11 @@ export const lightTheme: DefaultTheme = {
MODAL_BACKGROUND: "#FFFFFF",
TEXT_NORMAL: "#2e3338",
TEXT_POSITIVE: "#008736",
} as const;
};
const themeDs = {
...lightTheme,
...darkTheme,
};
export default themeDs;
+184
View File
@@ -0,0 +1,184 @@
import React from "react";
import { useRouter } from "next/router";
import toast from "react-hot-toast";
import {
AiOutlineCloudSync,
AiOutlineCloudUpload,
AiOutlineLink,
AiOutlineLock,
AiOutlineUnlock,
} from "react-icons/ai";
import { VscAccount } from "react-icons/vsc";
import { saveJson, updateJson } from "src/services/db/json";
import useJson from "src/store/useJson";
import useModal from "src/store/useModal";
import useStored from "src/store/useStored";
import useUser from "src/store/useUser";
import styled from "styled-components";
const StyledBottomBar = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
border-top: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
background: ${({ theme }) => theme.BACKGROUND_TERTIARY};
max-height: 28px;
height: 28px;
padding: 0 6px;
`;
const StyledLeft = styled.div`
display: flex;
align-items: center;
justify-content: left;
gap: 4px;
`;
const StyledRight = styled.div`
display: flex;
align-items: center;
justify-content: right;
gap: 4px;
`;
const StyledBottomBarItem = styled.button`
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};
&:hover:not(&:disabled) {
background-image: linear-gradient(rgba(0, 0, 0, 0.1) 0 0);
color: ${({ theme }) => theme.INTERACTIVE_HOVER};
}
&:disabled {
opacity: 0.4;
cursor: progress;
}
`;
const StyledImg = styled.img<{ light: boolean }>`
filter: ${({ light }) => light && "invert(100%)"};
`;
export const BottomBar = () => {
const { replace, query } = useRouter();
const data = useJson(state => state.data);
const user = useUser(state => state.user);
const lightmode = useStored(state => state.lightmode);
const hasChanges = useJson(state => state.hasChanges);
const getJson = useJson(state => state.getJson);
const setVisible = useModal(state => state.setVisible);
const setHasChanges = useJson(state => state.setHasChanges);
const [isPrivate, setIsPrivate] = React.useState(false);
const [isUpdating, setIsUpdating] = React.useState(false);
React.useEffect(() => {
setIsPrivate(data?.private ?? false);
}, [data]);
const handleSaveJson = React.useCallback(async () => {
if (!user) return setVisible("login")(true);
if (hasChanges) {
try {
setIsUpdating(true);
toast.loading("Saving JSON...", { id: "jsonSave" });
const res = await saveJson({ id: query.json as string, data: getJson() });
if (res.errors && res.errors.items.length > 0) throw res.errors;
if (res.data._id) replace({ query: { json: res.data._id } });
toast.success("JSON saved to cloud", { id: "jsonSave" });
setHasChanges(false);
} catch (error: any) {
if (error?.items?.length > 0) {
return toast.error(error.items[0].message, { id: "jsonSave", duration: 5000 });
}
toast.error("Failed to save JSON!", { id: "jsonSave" });
} finally {
setIsUpdating(false);
}
}
}, [getJson, hasChanges, query.json, replace, setHasChanges, setVisible, user]);
const handleLoginClick = () => {
if (user) return setVisible("account")(true);
else setVisible("login")(true);
};
const setPrivate = async () => {
try {
if (!query.json) return handleSaveJson();
if (!isPrivate && user?.type === 0) {
return window.open("https://jsoncrack.com/pricing", "_blank");
}
setIsUpdating(true);
const res = await updateJson(query.json as string, { private: !isPrivate });
if (!res.errors?.items.length) {
setIsPrivate(res.data.private);
toast.success(`Document set to ${isPrivate ? "public" : "private"}.`);
} else throw res.errors;
} catch (error) {
toast.error("An error occured while updating document!");
} finally {
setIsUpdating(false);
}
};
return (
<StyledBottomBar>
<StyledLeft>
<StyledBottomBarItem onClick={handleLoginClick}>
<VscAccount />
{user ? user.name : "Login"}
</StyledBottomBarItem>
<StyledBottomBarItem onClick={handleSaveJson} disabled={isUpdating}>
{hasChanges ? <AiOutlineCloudUpload /> : <AiOutlineCloudSync />}
{hasChanges ? "Unsaved Changes" : "Saved"}
</StyledBottomBarItem>
{data && (
<>
{typeof data.private !== "undefined" && (
<StyledBottomBarItem onClick={setPrivate} disabled={isUpdating}>
{isPrivate ? <AiOutlineLock /> : <AiOutlineUnlock />}
{isPrivate ? "Private" : "Public"}
</StyledBottomBarItem>
)}
<StyledBottomBarItem onClick={() => setVisible("share")(true)}>
<AiOutlineLink />
Share
</StyledBottomBarItem>
</>
)}
</StyledLeft>
<StyledRight>
<a
href="https://www.altogic.com/?utm_source=jsoncrack&utm_medium=referral&utm_campaign=sponsorship"
rel="sponsored noreferrer"
target="_blank"
>
<StyledBottomBarItem>
Powered by
<StyledImg
height="20"
src="https://www.altogic.com/img/logo_dark.svg"
alt="powered by altogic"
light={lightmode}
/>
</StyledBottomBarItem>
</a>
</StyledRight>
</StyledBottomBar>
);
};
+2 -4
View File
@@ -11,12 +11,10 @@ const StyledEditorWrapper = styled.div`
user-select: none;
`;
export const JsonEditor: React.FC = () => {
const [hasError, setHasError] = React.useState(false);
return (
<StyledEditorWrapper>
<ErrorContainer hasError={hasError} />
<MonacoEditor setHasError={setHasError} />
<ErrorContainer />
<MonacoEditor />
</StyledEditorWrapper>
);
};
@@ -1,17 +1,16 @@
import React from "react";
import { Graph } from "src/components/Graph";
import { NodeModal } from "src/containers/Modals/NodeModal";
import useGraph from "src/hooks/store/useGraph";
import useGraph from "src/store/useGraph";
export const GraphCanvas = ({ isWidget = false }: { isWidget?: boolean }) => {
const [isModalVisible, setModalVisible] = React.useState(false);
const [selectedNode, setSelectedNode] = React.useState<[string, string][]>([]);
const openModal = React.useCallback(() => setModalVisible(true), []);
const collapsedNodes = useGraph(state => state.collapsedNodes);
const collapsedEdges = useGraph(state => state.collapsedEdges);
const loading = useGraph(state => state.loading);
const openModal = React.useCallback(() => setModalVisible(true), []);
React.useEffect(() => {
const nodeList = collapsedNodes.map(id => `[id$="node-${id}"]`);
@@ -22,27 +21,23 @@ export const GraphCanvas = ({ isWidget = false }: { isWidget?: boolean }) => {
if (nodeList.length) {
const selectedNodes = document.querySelectorAll(nodeList.join(","));
const selectedEdges = document.querySelectorAll(edgeList.join(","));
selectedNodes.forEach(node => node.classList.add("hide"));
}
if (edgeList.length) {
const selectedEdges = document.querySelectorAll(edgeList.join(","));
selectedEdges.forEach(edge => edge.classList.add("hide"));
}
}, [collapsedNodes, collapsedEdges, loading]);
}, [collapsedNodes, collapsedEdges]);
return (
<>
<Graph
openModal={openModal}
setSelectedNode={setSelectedNode}
isWidget={isWidget}
<Graph openModal={openModal} setSelectedNode={setSelectedNode} isWidget={isWidget} />
<NodeModal
selectedNode={selectedNode}
visible={isModalVisible}
closeModal={() => setModalVisible(false)}
/>
{!isWidget && (
<NodeModal
selectedNode={selectedNode}
visible={isModalVisible}
closeModal={() => setModalVisible(false)}
/>
)}
</>
);
};
+9 -9
View File
@@ -3,7 +3,7 @@ import dynamic from "next/dynamic";
import { Allotment } from "allotment";
import "allotment/dist/style.css";
import { JsonEditor } from "src/containers/Editor/JsonEditor";
import useConfig from "src/hooks/store/useConfig";
import useGraph from "src/store/useGraph";
import styled from "styled-components";
export const StyledEditor = styled(Allotment)`
@@ -17,25 +17,25 @@ const LiveEditor = dynamic(() => import("src/containers/Editor/LiveEditor"), {
});
const Panes: React.FC = () => {
const hideEditor = useConfig(state => state.hideEditor);
const setConfig = useConfig(state => state.setConfig);
const isMobile = window.innerWidth <= 768;
const fullscreen = useGraph(state => state.fullscreen);
const toggleFullscreen = useGraph(state => state.toggleFullscreen);
const isMobile = React.useMemo(() => window.innerWidth <= 768, []);
React.useEffect(() => {
if (isMobile) setConfig("hideEditor", true);
}, [isMobile, setConfig]);
if (isMobile) toggleFullscreen(true);
}, [isMobile, toggleFullscreen]);
return (
<StyledEditor proportionalLayout={false} vertical={isMobile}>
<Allotment.Pane
preferredSize={isMobile ? "100%" : 400}
minSize={hideEditor ? 0 : 300}
minSize={fullscreen ? 0 : 300}
maxSize={isMobile ? Infinity : 800}
visible={!hideEditor}
visible={!fullscreen}
>
<JsonEditor />
</Allotment.Pane>
<Allotment.Pane minSize={0} maxSize={isMobile && !hideEditor ? 0 : Infinity}>
<Allotment.Pane minSize={0} maxSize={isMobile && !fullscreen ? 0 : Infinity}>
<LiveEditor />
</Allotment.Pane>
</StyledEditor>
+10 -24
View File
@@ -2,12 +2,10 @@ import React from "react";
import { AiOutlineFullscreen, AiOutlineMinus, AiOutlinePlus } from "react-icons/ai";
import { FiDownload } from "react-icons/fi";
import { MdCenterFocusWeak } from "react-icons/md";
import { TbSettings } from "react-icons/tb";
import { SearchInput } from "src/components/SearchInput";
import { SettingsModal } from "src/containers/Modals/SettingsModal";
import useConfig from "src/hooks/store/useConfig";
import useGraph from "src/store/useGraph";
import useModal from "src/store/useModal";
import styled from "styled-components";
import { DownloadModal } from "../Modals/DownloadModal";
export const StyledTools = styled.div`
position: relative;
@@ -48,16 +46,15 @@ const StyledToolElement = styled.button`
`;
export const Tools: React.FC = () => {
const [settingsVisible, setSettingsVisible] = React.useState(false);
const [isDownloadVisible, setDownloadVisible] = React.useState(false);
const setVisible = useModal(state => state.setVisible);
const hideEditor = useConfig(state => state.hideEditor);
const setConfig = useConfig(state => state.setConfig);
const fullscreen = useGraph(state => state.fullscreen);
const toggleFullscreen = useGraph(state => state.toggleFullscreen);
const zoomIn = useConfig(state => state.zoomIn);
const zoomOut = useConfig(state => state.zoomOut);
const centerView = useConfig(state => state.centerView);
const toggleEditor = () => setConfig("hideEditor", !hideEditor);
const zoomIn = useGraph(state => state.zoomIn);
const zoomOut = useGraph(state => state.zoomOut);
const centerView = useGraph(state => state.centerView);
const toggleEditor = () => toggleFullscreen(!fullscreen);
return (
<>
@@ -65,17 +62,8 @@ export const Tools: React.FC = () => {
<StyledToolElement aria-label="fullscreen" onClick={toggleEditor}>
<AiOutlineFullscreen />
</StyledToolElement>
<StyledToolElement
aria-label="settings"
onClick={() => setSettingsVisible(true)}
>
<TbSettings />
</StyledToolElement>
<SearchInput />
<StyledToolElement
aria-label="save"
onClick={() => setDownloadVisible(true)}
>
<StyledToolElement aria-label="save" onClick={() => setVisible("download")(true)}>
<FiDownload />
</StyledToolElement>
<StyledToolElement aria-label="center canvas" onClick={centerView}>
@@ -88,8 +76,6 @@ export const Tools: React.FC = () => {
<AiOutlinePlus />
</StyledToolElement>
</StyledTools>
<DownloadModal visible={isDownloadVisible} setVisible={setDownloadVisible} />
<SettingsModal visible={settingsVisible} setVisible={setSettingsVisible} />
</>
);
};
+101 -81
View File
@@ -1,20 +1,23 @@
import React from "react";
import Head from "next/head";
import Link from "next/link";
import { FaGithub, FaHeart, FaLinkedin, FaTwitter } from "react-icons/fa";
import Script from "next/script";
import { AiOutlineRight } from "react-icons/ai";
import {
HiCursorClick,
HiLightningBolt,
HiOutlineDownload,
HiOutlineSearchCircle,
} from "react-icons/hi";
import { IoRocketSharp } from "react-icons/io5";
import { SiVisualstudiocode } from "react-icons/si";
import { TwitterTweetEmbed } from "react-twitter-embed";
import { CarbonAds } from "src/components/CarbonAds";
import { Footer } from "src/components/Footer";
import { Producthunt } from "src/components/Producthunt";
import { Sponsors } from "src/components/Sponsors";
import { GoalsModal } from "src/containers/Modals/GoalsModal";
import pkg from "../../../package.json";
import { SupportButton } from "src/components/SupportButton";
import { baseURL } from "src/constants/data";
import { PricingCards } from "../PricingCards";
import * as Styles from "./styles";
const Navbar = () => (
@@ -33,6 +36,9 @@ const Navbar = () => (
>
GitHub
</Styles.StyledNavLink>
<Link href="docs" passHref>
<Styles.StyledNavLink>Documentation</Styles.StyledNavLink>
</Link>
</Styles.StyledNavbar>
);
@@ -42,34 +48,31 @@ const HeroSection = () => {
return (
<Styles.StyledHeroSection id="main">
<Styles.StyledTitle>
<Styles.StyledGradientText>JSON</Styles.StyledGradientText> Crack
<Styles.StyledGradientText>JSON</Styles.StyledGradientText> CRACK
</Styles.StyledTitle>
<Styles.StyledSubTitle>
Seamlessly visualize your JSON data{" "}
<Styles.StyledHighlightedText>instantly</Styles.StyledHighlightedText> into
graphs.
<Styles.StyledHighlightedText>instantly</Styles.StyledHighlightedText> into graphs.
</Styles.StyledSubTitle>
<Styles.StyledMinorTitle>Paste - Import - Fetch!</Styles.StyledMinorTitle>
<Styles.StyledButton rel="prefetch" href="/editor" target="_blank" link>
<Styles.StyledButton href="/editor" link>
GO TO EDITOR
<AiOutlineRight strokeWidth="80" />
</Styles.StyledButton>
<Styles.StyledButtonWrapper>
<Styles.StyledSponsorButton onClick={() => setModalVisible(true)}>
Help JSON Crack&apos;s Goals
<FaHeart />
<Styles.StyledSponsorButton href="/pricing" link>
GET PREMIUM
<IoRocketSharp />
</Styles.StyledSponsorButton>
<Link
<Styles.StyledSponsorButton
href="https://marketplace.visualstudio.com/items?itemName=AykutSarac.jsoncrack-vscode"
passHref
link
isBlue
>
<Styles.StyledSponsorButton isBlue>
GET IT ON VS CODE
<SiVisualstudiocode />
</Styles.StyledSponsorButton>
</Link>
<GoalsModal visible={isModalVisible} setVisible={setModalVisible} />
GET IT ON VS CODE
<SiVisualstudiocode />
</Styles.StyledSponsorButton>
</Styles.StyledButtonWrapper>
</Styles.StyledHeroSection>
);
@@ -96,9 +99,9 @@ const FeaturesSection = () => (
</Styles.StyledCardIcon>
<Styles.StyledCardTitle>EASY-TO-USE</Styles.StyledCardTitle>
<Styles.StyledCardDescription>
Don&apos;t even bother to update your schema to view your JSON into graphs;
directly paste, import or fetch! JSON Crack helps you to visualize without
any additional values and save your time.
We believe that powerful software doesn&apos;t have to be difficult to use. That&apos;s why
we&apos;ve designed our app to be as intuitive and easy-to-use as possible. You can quickly
and easily load your JSON data and start exploring and analyzing it right away!
</Styles.StyledCardDescription>
</Styles.StyledSectionCard>
@@ -108,9 +111,9 @@ const FeaturesSection = () => (
</Styles.StyledCardIcon>
<Styles.StyledCardTitle>SEARCH</Styles.StyledCardTitle>
<Styles.StyledCardDescription>
Have a huge file of values, keys or arrays? Worry no more, type in the
keyword you are looking for into search input and it will take you to each
node with matching result highlighting the line to understand better!
Have a huge file of values, keys or arrays? Worry no more, type in the keyword you are
looking for into search input and it will take you to each node with matching result
highlighting the line to understand better!
</Styles.StyledCardDescription>
</Styles.StyledSectionCard>
@@ -120,9 +123,9 @@ const FeaturesSection = () => (
</Styles.StyledCardIcon>
<Styles.StyledCardTitle>DOWNLOAD</Styles.StyledCardTitle>
<Styles.StyledCardDescription>
Download the graph to your local machine and use it wherever you want, to
your blogs, website or make it a poster and paste to the wall. Who
wouldn&apos;t want to see a JSON Crack graph onto their wall, eh?
Download the graph to your local machine and use it wherever you want, to your blogs,
website or make it a poster and paste to the wall. Who wouldn&apos;t want to see a JSON
Crack graph onto their wall, eh?
</Styles.StyledCardDescription>
</Styles.StyledSectionCard>
@@ -132,9 +135,9 @@ const FeaturesSection = () => (
</Styles.StyledCardIcon>
<Styles.StyledCardTitle>LIVE</Styles.StyledCardTitle>
<Styles.StyledCardDescription>
With Microsoft&apos;s Monaco Editor which is also used by VS Code, easily
edit your JSON and directly view through the graphs. Also there&apos;s a JSON
validator above of it to make sure there is no type error.
With Microsoft&apos;s Monaco Editor which is also used by VS Code, easily edit your JSON and
directly view through the graphs. Also there&apos;s a JSON validator above of it to make
sure there is no type error.
</Styles.StyledCardDescription>
</Styles.StyledSectionCard>
</Styles.StyledFeaturesSection>
@@ -142,19 +145,38 @@ const FeaturesSection = () => (
const GitHubSection = () => (
<Styles.StyledSection id="github" reverse>
<TwitterTweetEmbed
tweetId="1519363257794015233"
options={{
width: "600",
align: "center",
}}
/>
<Styles.StyledTwitterQuote>
<blockquote className="twitter-tweet" data-lang="en" data-dnt="true" data-theme="light">
<p lang="en" dir="ltr">
Looking to understand or explore some JSON? Just paste or upload to visualize it as a
graph with <a href="https://t.co/HlKSrhKryJ">https://t.co/HlKSrhKryJ</a> 😍 <br />
<br />
Thanks to <a href="https://twitter.com/aykutsarach?ref_src=twsrc%5Etfw">
@aykutsarach
</a>! <a href="https://t.co/0LyPUL8Ezz">pic.twitter.com/0LyPUL8Ezz</a>
</p>
&mdash; GitHub (@github){" "}
<a href="https://twitter.com/github/status/1519363257794015233?ref_src=twsrc%5Etfw">
April 27, 2022
</a>
</blockquote>{" "}
<Script async src="https://platform.twitter.com/widgets.js" charSet="utf-8"></Script>
</Styles.StyledTwitterQuote>
<Styles.StyledSectionArea>
<Styles.StyledSubTitle>Open Source Community</Styles.StyledSubTitle>
<Styles.StyledMinorTitle>
Join the Open Source Community by suggesting new ideas, support by
contributing; implementing new features, fixing bugs and doing changes minor
to major!
At JSON Crack, we believe in the power of open source software and the communities that
support it. That&apos;s why we&apos;re proud to be part of the open source community and to
contribute to the development and growth of open source tools and technologies.
<br />
<br /> As part of our commitment to the open source community, we&apos;ve made our app
freely available to anyone who wants to use it, and we welcome contributions from anyone
who&apos;s interested in helping to improve it. Whether you&apos;re a developer, a data
scientist, or just someone who&apos;s passionate about open source, we&apos;d love to have
you join our community and help us make JSON Crack the best it can be.
<br />
<br /> So why not join us and become part of the JSON Crack open source community today? We
can&apos;t wait to see what we can accomplish together!
</Styles.StyledMinorTitle>
<Styles.StyledButton
href="https://github.com/AykutSarac/jsoncrack.com"
@@ -172,12 +194,44 @@ const EmbedSection = () => (
<Styles.StyledSectionArea>
<Styles.StyledSubTitle>Embed Into Your Website</Styles.StyledSubTitle>
<Styles.StyledMinorTitle>
Easily embed the JSON Crack graph into your website to showcase your
visitors, blog readers or anybody else!
JSON Crack provides users with the necessary code to embed the app into a website easily
using an iframe. This code can be easily copied and pasted into the desired location on the
website, allowing users to quickly and easily integrate JSON Crack into existing workflows.
<br />
<br /> Once the app is embedded, users can use it to view and analyze JSON data directly on
the website. This can be useful for a variety of purposes, such as quickly checking the
structure of a JSON file or verifying the data contained within it. JSON Crack&apos;s
intuitive interface makes it easy to navigate and understand even complex JSON data, making
it a valuable tool for anyone working with JSON.
</Styles.StyledMinorTitle>
<Styles.StyledButton href="/docs" status="SECONDARY" link>
LEARN TO EMBED
</Styles.StyledButton>
</Styles.StyledSectionArea>
<div>
<Styles.StyledIframge src="https://jsoncrack.com/widget"></Styles.StyledIframge>
<Styles.StyledIframge
src={`${baseURL}/widget`}
onLoad={e => {
const frame = e.currentTarget.contentWindow;
setTimeout(() => {
frame?.postMessage(
{
json: JSON.stringify({
"random images": [
"https://random.imagecdn.app/50/50?.png",
"https://random.imagecdn.app/80/80?.png",
"https://random.imagecdn.app/100/100?.png",
],
}),
options: {
theme: "dark",
},
},
"*"
);
}, 500);
}}
></Styles.StyledIframge>
</div>
</Styles.StyledSection>
);
@@ -217,42 +271,6 @@ const SponsorSection = () => (
</Styles.StyledSponsorSection>
);
const Footer = () => (
<Styles.StyledFooter>
<Styles.StyledFooterText>
© 2022 JSON Crack - {pkg.version}
</Styles.StyledFooterText>
<Styles.StyledIconLinks>
<Styles.StyledNavLink
href="https://github.com/AykutSarac/jsoncrack.com"
rel="external"
target="_blank"
aria-label="github"
>
<FaGithub size={26} />
</Styles.StyledNavLink>
<Styles.StyledNavLink
href="https://www.linkedin.com/in/aykutsarac/"
rel="me"
target="_blank"
aria-label="linkedin"
>
<FaLinkedin size={26} />
</Styles.StyledNavLink>
<Styles.StyledNavLink
href="https://twitter.com/jsoncrack"
rel="me"
target="_blank"
aria-label="twitter"
>
<FaTwitter size={26} />
</Styles.StyledNavLink>
</Styles.StyledIconLinks>
</Styles.StyledFooter>
);
const Home: React.FC = () => {
return (
<Styles.StyledHome>
@@ -265,8 +283,10 @@ const Home: React.FC = () => {
<FeaturesSection />
<GitHubSection />
<EmbedSection />
<PricingCards />
<SupportSection />
<SponsorSection />
<SupportButton />
<Footer />
</Styles.StyledHome>
);
+76 -50
View File
@@ -4,6 +4,49 @@ import styled from "styled-components";
export const StyledButtonWrapper = styled.div`
display: flex;
gap: 18px;
@media only screen and (max-width: 768px) {
display: none;
}
`;
export const StyledTwitterQuote = styled.div`
width: 100%;
height: 100%;
blockquote.twitter-tweet {
display: inline-block;
font-family: "Helvetica Neue", Roboto, "Segoe UI", Calibri, sans-serif;
font-size: 12px;
font-weight: bold;
line-height: 16px;
border-color: #eee #ddd #bbb;
border-radius: 5px;
border-style: solid;
border-width: 1px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
margin: 10px 5px;
padding: 0 16px 16px 16px;
max-width: 468px;
}
blockquote.twitter-tweet p {
font-size: 16px;
font-weight: normal;
line-height: 20px;
}
blockquote.twitter-tweet a {
color: inherit;
font-weight: normal;
text-decoration: none;
outline: 0 none;
}
blockquote.twitter-tweet a:hover,
blockquote.twitter-tweet a:focus {
text-decoration: underline;
}
`;
export const StyledImageWrapper = styled.div`
@@ -31,13 +74,7 @@ export const StyledHome = styled.div`
export const StyledGradientText = styled.span`
background: #ffb76b;
background: linear-gradient(
to right,
#ffb76b 0%,
#ffa73d 30%,
#ff7c00 60%,
#ff7f04 100%
);
background: linear-gradient(to right, #ffb76b 0%, #ffa73d 30%, #ff7c00 60%, #ff7f04 100%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
@@ -66,6 +103,10 @@ export const StyledHeroSection = styled.section`
gap: 1.5em;
min-height: 40vh;
padding: 0 3%;
h2 {
margin-bottom: 25px;
}
`;
export const StyledNavLink = styled.a`
@@ -83,12 +124,12 @@ export const StyledNavLink = styled.a`
`;
export const StyledTitle = styled.h1`
font-size: 5rem;
font-weight: 900;
margin: 0;
font-size: min(10vw, 64px);
@media only screen and (max-width: 768px) {
font-size: 3rem;
font-size: 2.5rem;
}
`;
@@ -100,14 +141,14 @@ export const StyledSubTitle = styled.h2`
margin: 0;
@media only screen and (max-width: 768px) {
font-size: 1.75rem;
font-size: 1.5rem;
}
`;
export const StyledMinorTitle = styled.p`
color: #b4b4b4;
text-align: center;
font-size: 1.25rem;
font-size: 1rem;
margin: 0;
letter-spacing: 1.2px;
@@ -119,11 +160,12 @@ export const StyledMinorTitle = styled.p`
export const StyledButton = styled(Button)`
background: ${({ status }) => !status && "#a13cc2"};
padding: 12px 24px;
height: 46px;
div {
font-family: "Roboto", sans-serif;
font-family: "Mona Sans";
font-weight: 700;
font-size: 16px;
font-size: 1rem;
}
`;
@@ -150,28 +192,38 @@ export const StyledSponsorButton = styled(Button)<{ isBlue?: boolean }>`
color: white;
}
}
@media only screen and (max-width: 768px) {
display: ${({ isBlue }) => isBlue && "none"};
}
`;
export const StyledFeaturesSection = styled.section`
display: flex;
max-width: 85%;
display: grid;
margin: 0 auto;
gap: 2rem;
padding: 50px 3%;
max-width: 60%;
justify-content: center;
grid-template-columns: repeat(2, 40%);
grid-template-rows: repeat(2, 1fr);
grid-column-gap: 60px;
grid-row-gap: 60px;
@media only screen and (max-width: 768px) {
flex-direction: column;
max-width: 80%;
display: flex;
}
`;
export const StyledSectionCard = styled.div`
text-align: center;
flex: 1;
border: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
background: rgb(48, 0, 65);
background: linear-gradient(
138deg,
rgba(48, 0, 65, 0.8870141806722689) 0%,
rgba(72, 12, 84, 0.40802258403361347) 33%,
rgba(65, 8, 92, 0.6012998949579832) 100%
);
border-radius: 6px;
padding: 16px;
`;
export const StyledCardTitle = styled.div`
@@ -216,7 +268,7 @@ export const StyledSection = styled.section<{ reverse?: boolean }>`
width: 100%;
}
@media only screen and (max-width: 768px) {
@media only screen and (max-width: 1200px) {
flex-direction: ${({ reverse }) => (reverse ? "column-reverse" : "column")};
max-width: 80%;
}
@@ -239,8 +291,7 @@ export const StyledSectionArea = styled.div`
width: 100%;
align-items: center;
h2,
p {
h2 {
text-align: center;
}
}
@@ -258,7 +309,7 @@ export const StyledSponsorSection = styled.section`
padding: 50px 3%;
@media only screen and (max-width: 768px) {
max-width: 80%;
max-width: 90%;
}
`;
@@ -288,7 +339,6 @@ export const StyledPreviewSection = styled.section`
@media only screen and (max-width: 768px) {
display: none;
max-width: 80%;
}
`;
@@ -299,30 +349,6 @@ export const StyledImage = styled.img`
filter: drop-shadow(0px 0px 12px rgba(255, 255, 255, 0.6));
`;
export const StyledFooter = styled.footer`
display: flex;
flex-direction: row;
justify-content: space-between;
width: 80%;
margin: 0 auto;
padding: 30px 3%;
border-top: 1px solid #b4b4b4;
opacity: 0.7;
`;
export const StyledFooterText = styled.p`
color: #b4b4b4;
`;
export const StyledIconLinks = styled.div`
display: flex;
gap: 20px;
${StyledNavLink} {
color: unset;
}
`;
export const StyledHighlightedText = styled.span`
text-decoration: underline;
text-decoration-style: dashed;
+51
View File
@@ -0,0 +1,51 @@
import React from "react";
import { AccountModal } from "src/containers/Modals/AccountModal";
import { ClearModal } from "src/containers/Modals/ClearModal";
import { CloudModal } from "src/containers/Modals/CloudModal";
import { DownloadModal } from "src/containers/Modals/DownloadModal";
import { ImportModal } from "src/containers/Modals/ImportModal";
import { LoginModal } from "src/containers/Modals/LoginModal";
import { SettingsModal } from "src/containers/Modals/SettingsModal";
import { ShareModal } from "src/containers/Modals/ShareModal";
import useModal from "src/store/useModal";
import shallow from "zustand/shallow";
export const ModalController = () => {
const setVisible = useModal(state => state.setVisible);
const [
importModal,
clearModal,
downloadModal,
settingsModal,
cloudModal,
accountModal,
loginModal,
shareModal,
] = useModal(
state => [
state.import,
state.clear,
state.download,
state.settings,
state.cloud,
state.account,
state.login,
state.share,
],
shallow
);
return (
<>
<ImportModal visible={importModal} setVisible={setVisible("import")} />
<ClearModal visible={clearModal} setVisible={setVisible("clear")} />
<DownloadModal visible={downloadModal} setVisible={setVisible("download")} />
<SettingsModal visible={settingsModal} setVisible={setVisible("settings")} />
<CloudModal visible={cloudModal} setVisible={setVisible("cloud")} />
<AccountModal visible={accountModal} setVisible={setVisible("account")} />
<LoginModal visible={loginModal} setVisible={setVisible("login")} />
<ShareModal visible={shareModal} setVisible={setVisible("share")} />
</>
);
};
@@ -0,0 +1,143 @@
import React from "react";
import { IoRocketSharp } from "react-icons/io5";
import { MdVerified } from "react-icons/md";
import { Button } from "src/components/Button";
import { Modal, ModalProps } from "src/components/Modal";
import useUser from "src/store/useUser";
import styled from "styled-components";
const StyledTitle = styled.p`
display: flex;
align-items: center;
color: ${({ theme }) => theme.TEXT_POSITIVE};
flex: 1;
font-weight: 700;
margin-top: 0;
&::after {
background: ${({ theme }) => theme.TEXT_POSITIVE};
height: 1px;
content: "";
-webkit-box-flex: 1;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
margin-left: 4px;
opacity: 0.6;
}
`;
const StyledAccountWrapper = styled.div`
display: flex;
flex-wrap: wrap;
gap: 20px;
background: ${({ theme }) => theme.BACKGROUND_TERTIARY};
padding: 16px;
border-radius: 6px;
button {
flex-basis: 100%;
}
`;
const StyledAvatar = styled.img`
border-radius: 100%;
`;
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px 0;
font-size: 12px;
line-height: 16px;
font-weight: 600;
color: ${({ theme }) => theme.INTERACTIVE_NORMAL};
& > div {
font-weight: 400;
font-size: 14px;
color: ${({ theme }) => theme.INTERACTIVE_ACTIVE};
}
`;
const AccountView: React.FC<Pick<ModalProps, "setVisible">> = ({ setVisible }) => {
const user = useUser(state => state.user);
const isPremium = useUser(state => state.isPremium());
const logout = useUser(state => state.logout);
const onImgFail = (e: React.SyntheticEvent<HTMLImageElement>) => {
e.currentTarget.setAttribute("src", `https://ui-avatars.com/api/?name=${user?.name}`);
};
return (
<>
<Modal.Header>Account</Modal.Header>
<Modal.Content>
<StyledTitle>Hello, {user?.name}!</StyledTitle>
<StyledAccountWrapper>
<StyledAvatar
width="60"
height="60"
src={user?.profilePicture}
alt={user?.name}
onError={onImgFail}
/>
<StyledContainer>
USERNAME
<div>{user?.name}</div>
</StyledContainer>
<StyledContainer>
ACCOUNT STATUS
<div>
{isPremium ? "PREMIUM " : "Free"}
{isPremium && <MdVerified />}
</div>
</StyledContainer>
<StyledContainer>
EMAIL
<div>{user?.email}</div>
</StyledContainer>
<StyledContainer>
REGISTRATION
<div>{user?.signUpAt && new Date(user.signUpAt).toDateString()}</div>
</StyledContainer>
{isPremium ? (
<Button
status="DANGER"
block
onClick={() => window.open("https://patreon.com/jsoncrack", "_blank")}
>
<IoRocketSharp />
Cancel Subscription
</Button>
) : (
<Button href="/pricing" status="TERTIARY" block link>
<IoRocketSharp />
UPGRADE TO PREMIUM!
</Button>
)}
</StyledAccountWrapper>
</Modal.Content>
<Modal.Controls setVisible={setVisible}>
<Button
status="DANGER"
onClick={() => {
logout();
setVisible(false);
}}
>
Log Out
</Button>
</Modal.Controls>
</>
);
};
export const AccountModal: React.FC<ModalProps> = ({ setVisible, visible }) => {
return (
<Modal visible={visible} setVisible={setVisible}>
<AccountView setVisible={setVisible} />
</Modal>
);
};
+12 -6
View File
@@ -1,22 +1,28 @@
import React from "react";
import toast from "react-hot-toast";
import { useRouter } from "next/router";
import { Button } from "src/components/Button";
import { Modal, ModalProps } from "src/components/Modal";
import useConfig from "src/hooks/store/useConfig";
import { deleteJson } from "src/services/db/json";
import useJson from "src/store/useJson";
export const ClearModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
const setJson = useConfig(state => state.setJson);
const setJson = useJson(state => state.setJson);
const { query, replace } = useRouter();
const handleClear = () => {
setJson("{}");
toast.success(`Cleared JSON and removed from memory.`);
setVisible(false);
if (typeof query.json === "string") {
deleteJson(query.json);
replace("/editor");
}
};
return (
<Modal visible={visible} setVisible={setVisible}>
<Modal.Header>Clear JSON</Modal.Header>
<Modal.Content>Are you sure you want to clear JSON?</Modal.Content>
<Modal.Header>Delete JSON</Modal.Header>
<Modal.Content>Are you sure you want to delete JSON?</Modal.Content>
<Modal.Controls setVisible={setVisible}>
<Button status="DANGER" onClick={handleClear}>
Confirm
+270
View File
@@ -0,0 +1,270 @@
import React from "react";
import { useRouter } from "next/router";
import { useQuery } from "@tanstack/react-query";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import toast from "react-hot-toast";
import {
AiOutlineEdit,
AiOutlineInfoCircle,
AiOutlineLock,
AiOutlinePlus,
AiOutlineUnlock,
} from "react-icons/ai";
import { FaTrash } from "react-icons/fa";
import { IoRocketSharp } from "react-icons/io5";
import { Button } from "src/components/Button";
import { Modal, ModalProps } from "src/components/Modal";
import { Spinner } from "src/components/Spinner";
import { deleteJson, getAllJson, saveJson, updateJson } from "src/services/db/json";
import useJson from "src/store/useJson";
import useUser from "src/store/useUser";
import { Json } from "src/typings/altogic";
import styled from "styled-components";
dayjs.extend(relativeTime);
const StyledModalContent = styled.div`
display: flex;
flex-direction: column;
gap: 14px;
overflow: auto;
`;
const StyledJsonCard = styled.a<{ active?: boolean; create?: boolean }>`
display: ${({ create }) => (create ? "block" : "flex")};
align-items: center;
justify-content: space-between;
background: ${({ theme }) => theme.BLACK_SECONDARY};
border: 2px solid ${({ theme, active }) => (active ? theme.SEAGREEN : theme.BLACK_SECONDARY)};
border-radius: 5px;
overflow: hidden;
flex: 1;
height: 160px;
`;
const StyledInfo = styled.div`
padding: 4px 6px;
`;
const StyledTitle = styled.div`
display: flex;
align-items: center;
gap: 4px;
font-size: 14px;
font-weight: 500;
width: fit-content;
cursor: pointer;
span {
overflow: hidden;
text-overflow: ellipsis;
}
`;
const StyledDetils = styled.div`
display: flex;
align-items: center;
font-size: 12px;
gap: 4px;
`;
const StyledModal = styled(Modal)`
#modal-view {
display: none;
}
`;
const StyledDeleteButton = styled(Button)`
background: transparent;
`;
const StyledCreateWrapper = styled.div`
display: flex;
height: 100%;
gap: 6px;
align-items: center;
justify-content: center;
opacity: 0.6;
height: 45px;
font-size: 14px;
cursor: pointer;
`;
const StyledNameInput = styled.input`
background: transparent;
border: none;
outline: none;
width: 90%;
color: ${({ theme }) => theme.SEAGREEN};
font-weight: 600;
`;
const StyledInfoText = styled.span`
font-size: 10px;
color: ${({ theme }) => theme.INTERACTIVE_NORMAL};
svg {
vertical-align: text-top;
margin-right: 4px;
}
`;
const GraphCard: React.FC<{ data: Json; refetch: () => void; active: boolean }> = ({
data,
refetch,
active,
...props
}) => {
const [editMode, setEditMode] = React.useState(false);
const [name, setName] = React.useState(data.name);
const onSubmit = () => {
toast
.promise(updateJson(data._id, { name }), {
loading: "Updating document...",
error: "Error occured while updating document!",
success: `Renamed document to ${name}`,
})
.then(refetch);
setEditMode(false);
};
const onDeleteClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
toast
.promise(deleteJson(data._id), {
loading: "Deleting JSON file...",
error: "An error occured while deleting the file!",
success: `Deleted ${name}!`,
})
.then(refetch);
};
return (
<StyledJsonCard
href={`?json=${data._id}`}
as={editMode ? "div" : "a"}
active={active}
{...props}
>
<StyledInfo>
{editMode ? (
<form onSubmit={onSubmit}>
<StyledNameInput
value={name}
onChange={e => setName(e.currentTarget.value)}
onClick={e => e.preventDefault()}
autoFocus
/>
<input type="submit" hidden />
</form>
) : (
<StyledTitle
onClick={e => {
e.preventDefault();
setEditMode(true);
}}
>
<span>{name}</span>
<AiOutlineEdit />
</StyledTitle>
)}
<StyledDetils>
{data.private ? <AiOutlineLock /> : <AiOutlineUnlock />}
Last modified {dayjs(data.updatedAt).fromNow()}
</StyledDetils>
</StyledInfo>
<StyledDeleteButton onClick={onDeleteClick}>
<FaTrash />
</StyledDeleteButton>
</StyledJsonCard>
);
};
const CreateCard: React.FC<{ reachedLimit: boolean }> = ({ reachedLimit }) => {
const { replace } = useRouter();
const isPremium = useUser(state => state.isPremium());
const getJson = useJson(state => state.getJson);
const setHasChanges = useJson(state => state.setHasChanges);
const onCreate = async () => {
try {
toast.loading("Saving JSON...", { id: "jsonSave" });
const res = await saveJson({ data: getJson() });
if (res.errors && res.errors.items.length > 0) throw res.errors;
toast.success("JSON saved to cloud", { id: "jsonSave" });
setHasChanges(false);
replace({ query: { json: res.data._id } });
} catch (error: any) {
if (error?.items?.length > 0) {
return toast.error(error.items[0].message, { id: "jsonSave", duration: 7000 });
}
toast.error("Failed to save JSON!", { id: "jsonSave" });
}
};
if (!isPremium && reachedLimit)
return (
<StyledJsonCard href="/pricing" create>
<StyledCreateWrapper>
<IoRocketSharp size="18" />
You reached max limit, upgrade to premium for more!
</StyledCreateWrapper>
</StyledJsonCard>
);
return (
<StyledJsonCard onClick={onCreate} create>
<StyledCreateWrapper>
<AiOutlinePlus size="24" />
Create New JSON
</StyledCreateWrapper>
</StyledJsonCard>
);
};
export const CloudModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
const { isReady, query } = useRouter();
const { data, isFetching, refetch } = useQuery(["allJson", query], () => getAllJson(), {
enabled: isReady && visible,
});
return (
<StyledModal visible={visible} setVisible={setVisible}>
<Modal.Header>Saved On The Cloud</Modal.Header>
<Modal.Content>
<StyledModalContent>
{isFetching ? (
<Spinner />
) : (
<>
<CreateCard reachedLimit={data ? data?.data.result.length > 15 : false} />
{data?.data?.result?.map(json => (
<GraphCard
data={json}
key={json._id}
refetch={refetch}
active={query.json === json._id}
/>
))}
</>
)}
</StyledModalContent>
</Modal.Content>
<Modal.Controls setVisible={setVisible}>
<StyledInfoText>
<AiOutlineInfoCircle />
Cloud Save feature is for ease-of-access only and not recommended to store sensitive data,
we don&apos;t guarantee protection of your data.
</StyledInfoText>
</Modal.Controls>
</StyledModal>
);
};
@@ -7,7 +7,7 @@ import { FiCopy, FiDownload } from "react-icons/fi";
import { Button } from "src/components/Button";
import { Input } from "src/components/Input";
import { Modal, ModalProps } from "src/components/Modal";
import useConfig from "src/hooks/store/useConfig";
import useGraph from "src/store/useGraph";
import styled from "styled-components";
const ColorPickerStyles: Partial<TwitterPickerStylesProps> = {
@@ -93,7 +93,7 @@ const StyledColorIndicator = styled.div<{ color: string }>`
`;
export const DownloadModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
const setConfig = useConfig(state => state.setConfig);
const togglePerfMode = useGraph(state => state.togglePerfMode);
const [fileDetails, setFileDetails] = React.useState({
filename: "jsoncrack.com",
backgroundColor: "transparent",
@@ -103,7 +103,7 @@ export const DownloadModal: React.FC<ModalProps> = ({ visible, setVisible }) =>
const clipboardImage = async () => {
try {
toast.loading("Copying to clipboard...", { id: "toastClipboard" });
setConfig("performanceMode", false);
togglePerfMode(false);
const imageElement = document.querySelector("svg[id*='ref']") as HTMLElement;
@@ -126,14 +126,14 @@ export const DownloadModal: React.FC<ModalProps> = ({ visible, setVisible }) =>
} finally {
toast.dismiss("toastClipboard");
setVisible(false);
setConfig("performanceMode", true);
togglePerfMode(true);
}
};
const exportAsImage = async () => {
try {
toast.loading("Downloading...", { id: "toastDownload" });
setConfig("performanceMode", false);
togglePerfMode(false);
const imageElement = document.querySelector("svg[id*='ref']") as HTMLElement;
@@ -148,7 +148,7 @@ export const DownloadModal: React.FC<ModalProps> = ({ visible, setVisible }) =>
} finally {
toast.dismiss("toastDownload");
setVisible(false);
setConfig("performanceMode", true);
togglePerfMode(true);
}
};
@@ -1,76 +0,0 @@
import React from "react";
import { useRouter } from "next/router";
import { FaHeart, FaTwitter } from "react-icons/fa";
import { Button } from "src/components/Button";
import { Modal } from "src/components/Modal";
import styled from "styled-components";
const StyledTitle = styled.p`
display: flex;
align-items: center;
color: ${({ theme }) => theme.TEXT_POSITIVE};
flex: 1;
font-weight: 700;
font-family: "Catamaran", sans-serif;
margin-top: 0;
&::after {
background: ${({ theme }) => theme.TEXT_POSITIVE};
height: 1px;
content: "";
-webkit-box-flex: 1;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
margin-left: 4px;
opacity: 0.6;
}
`;
const ButtonsWrapper = styled.div`
display: flex;
padding: 40px 0 0;
gap: 20px;
`;
export const GoalsModal = ({ visible, setVisible }) => {
const { push } = useRouter();
return (
<Modal visible={visible} setVisible={setVisible}>
<Modal.Header>Help JSON Crack&apos;s Goals</Modal.Header>
<Modal.Content>
<StyledTitle>OUR GOAL</StyledTitle>
<b>JSON Crack&apos;s Goal</b> is to keep the service completely free and open
source for everyone! For the contiunity of our service and keep the new
updates coming we need your support to make that possible ❤️
<ButtonsWrapper>
<Button
href="https://github.com/sponsors/AykutSarac"
target="_blank"
rel="me"
status="DANGER"
block
link
>
<FaHeart />
Sponsor
</Button>
<Button
href={`https://twitter.com/intent/tweet?button=&url=${encodeURIComponent(
"https://jsoncrack.com"
)}&text=Looking+to+understand+or+explore+some+JSON?+Just+paste+or+upload+to+visualize+it+as+a+graph+with+JSON+Crack+😍&button=`}
rel="noreferrer"
status="SECONDARY"
block
link
>
<FaTwitter />
Share on Twitter
</Button>
</ButtonsWrapper>
</Modal.Content>
<Modal.Controls setVisible={setVisible} />
</Modal>
);
};
+5 -8
View File
@@ -4,7 +4,7 @@ import { AiOutlineUpload } from "react-icons/ai";
import { Button } from "src/components/Button";
import { Input } from "src/components/Input";
import { Modal, ModalProps } from "src/components/Modal";
import useConfig from "src/hooks/store/useConfig";
import useJson from "src/store/useJson";
import styled from "styled-components";
const StyledModalContent = styled(Modal.Content)`
@@ -33,6 +33,7 @@ const StyledUploadWrapper = styled.label`
`;
const StyledFileName = styled.span`
padding-top: 14px;
color: ${({ theme }) => theme.INTERACTIVE_NORMAL};
`;
@@ -42,7 +43,7 @@ const StyledUploadMessage = styled.h3`
`;
export const ImportModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
const setJson = useConfig(state => state.setJson);
const setJson = useJson(state => state.setJson);
const [url, setURL] = React.useState("");
const [jsonFile, setJsonFile] = React.useState<File | null>(null);
@@ -58,7 +59,7 @@ export const ImportModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
return fetch(url)
.then(res => res.json())
.then(json => {
setJson(JSON.stringify(json));
setJson(JSON.stringify(json, null, 2));
setVisible(false);
})
.catch(() => toast.error("Failed to fetch JSON!"))
@@ -99,11 +100,7 @@ export const ImportModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
</StyledUploadWrapper>
</StyledModalContent>
<Modal.Controls setVisible={setVisible}>
<Button
status="SECONDARY"
onClick={handleImportFile}
disabled={!(jsonFile || url)}
>
<Button status="SECONDARY" onClick={handleImportFile} disabled={!(jsonFile || url)}>
Import
</Button>
</Modal.Controls>
@@ -0,0 +1,26 @@
import React from "react";
import { AiOutlineGoogle } from "react-icons/ai";
import { altogic } from "src/api/altogic";
import { Button } from "src/components/Button";
import { Modal, ModalProps } from "src/components/Modal";
export const LoginModal: React.FC<ModalProps> = ({ setVisible, visible }) => {
const handleLoginClick = () => {
altogic.auth.signInWithProvider("google");
};
return (
<Modal visible={visible} setVisible={setVisible}>
<Modal.Header>Login</Modal.Header>
<Modal.Content>
<h2>Welcome Back!</h2>
<p>Login to unlock full potential of JSON Crack!</p>
<Button onClick={handleLoginClick} status="SECONDARY" block>
<AiOutlineGoogle size={24} />
Login with Google
</Button>
</Modal.Content>
<Modal.Controls setVisible={setVisible} />
</Modal>
);
};
+1 -3
View File
@@ -26,9 +26,7 @@ const StyledTextarea = styled.textarea`
`;
export const NodeModal = ({ selectedNode, visible, closeModal }: NodeModalProps) => {
const nodeData = Array.isArray(selectedNode)
? Object.fromEntries(selectedNode)
: selectedNode;
const nodeData = Array.isArray(selectedNode) ? Object.fromEntries(selectedNode) : selectedNode;
const handleClipboard = () => {
toast.success("Content copied to clipboard!");
+28 -23
View File
@@ -1,7 +1,7 @@
import React from "react";
import { Modal } from "src/components/Modal";
import { Modal, ModalProps } from "src/components/Modal";
import Toggle from "src/components/Toggle";
import useStored from "src/hooks/store/useStored";
import useStored from "src/store/useStored";
import styled from "styled-components";
import shallow from "zustand/shallow";
@@ -16,18 +16,26 @@ const StyledModalWrapper = styled.div`
gap: 20px;
`;
export const SettingsModal: React.FC<{
visible: boolean;
setVisible: React.Dispatch<React.SetStateAction<boolean>>;
}> = ({ visible, setVisible }) => {
export const SettingsModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
const lightmode = useStored(state => state.lightmode);
const setLightTheme = useStored(state => state.setLightTheme);
const [toggleHideCollapse, hideCollapse] = useStored(
state => [state.toggleHideCollapse, state.hideCollapse],
shallow
);
const [toggleHideChildrenCount, hideChildrenCount] = useStored(
state => [state.toggleHideChildrenCount, state.hideChildrenCount],
const [
toggleHideCollapse,
toggleChildrenCount,
toggleImagePreview,
hideCollapse,
childrenCount,
imagePreview,
] = useStored(
state => [
state.toggleHideCollapse,
state.toggleChildrenCount,
state.toggleImagePreview,
state.hideCollapse,
state.childrenCount,
state.imagePreview,
],
shallow
);
@@ -36,20 +44,17 @@ export const SettingsModal: React.FC<{
<Modal.Header>Settings</Modal.Header>
<Modal.Content>
<StyledModalWrapper>
<StyledToggle onChange={toggleImagePreview} checked={imagePreview}>
Live Image Preview
</StyledToggle>
<StyledToggle onChange={toggleHideCollapse} checked={hideCollapse}>
Hide Collapse/Expand Button
Display Collapse/Expand Button
</StyledToggle>
<StyledToggle
onChange={toggleHideChildrenCount}
checked={hideChildrenCount}
>
Hide Children Count
<StyledToggle onChange={toggleChildrenCount} checked={childrenCount}>
Display Children Count
</StyledToggle>
<StyledToggle
onChange={() => setLightTheme(!lightmode)}
checked={lightmode}
>
Enable Light Theme
<StyledToggle onChange={() => setLightTheme(!lightmode)} checked={lightmode}>
Light Theme
</StyledToggle>
</StyledModalWrapper>
</Modal.Content>
+24 -63
View File
@@ -1,26 +1,11 @@
import React from "react";
import { useRouter } from "next/router";
import { compress } from "compress-json";
import toast from "react-hot-toast";
import { BiErrorAlt } from "react-icons/bi";
import { Button } from "src/components/Button";
import { Input } from "src/components/Input";
import { Modal, ModalProps } from "src/components/Modal";
import { baseURL } from "src/constants/data";
import useConfig from "src/hooks/store/useConfig";
import styled from "styled-components";
const StyledWarning = styled.p``;
const StyledErrorWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: ${({ theme }) => theme.TEXT_DANGER};
font-weight: 600;
`;
const StyledFlex = styled.div`
display: flex;
gap: 12px;
@@ -45,21 +30,8 @@ const StyledContainer = styled.div`
`;
export const ShareModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
const json = useConfig(state => state.json);
const [encodedJson, setEncodedJson] = React.useState("");
const navigate = useRouter();
const embedText = `<iframe id="jsoncrackEmbed" src="${baseURL}/widget></iframe>`;
const shareURL = `${baseURL}/editor?json=${encodedJson}`;
React.useEffect(() => {
if (visible) {
const jsonEncode = compress(JSON.parse(json));
const jsonString = JSON.stringify(jsonEncode);
setEncodedJson(encodeURIComponent(jsonString));
}
}, [json, visible]);
const { push, query } = useRouter();
const shareURL = `https://jsoncrack.com/editor?json=${query.json}`;
const handleShare = (value: string) => {
navigator.clipboard.writeText(value);
@@ -67,43 +39,32 @@ export const ShareModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
setVisible(false);
};
const onEmbedClick = () => {
push("/docs");
setVisible(false);
};
return (
<Modal visible={visible} setVisible={setVisible}>
<Modal.Header>Create a Share Link</Modal.Header>
<Modal.Content>
{encodedJson.length > 5000 ? (
<StyledErrorWrapper>
<BiErrorAlt size={60} />
<StyledWarning>
Link size exceeds 5000 characters, unable to generate link for file of
this size!
</StyledWarning>
</StyledErrorWrapper>
) : (
<>
<StyledContainer>
Share Link
<StyledFlex>
<Input value={shareURL} type="url" readOnly />
<Button status="SECONDARY" onClick={() => handleShare(shareURL)}>
Copy
</Button>
</StyledFlex>
</StyledContainer>
<StyledContainer>
Embed into your website
<StyledFlex>
<Button
status="SUCCESS"
onClick={() => navigate.push("/embed")}
block
>
Learn How to Embed
</Button>
</StyledFlex>
</StyledContainer>
</>
)}
<StyledContainer>
Share Link
<StyledFlex>
<Input value={shareURL} type="url" readOnly />
<Button status="SECONDARY" onClick={() => handleShare(shareURL)}>
Copy
</Button>
</StyledFlex>
</StyledContainer>
<StyledContainer>
Embed into your website
<StyledFlex>
<Button status="SUCCESS" onClick={onEmbedClick} block>
Learn How to Embed
</Button>
</StyledFlex>
</StyledContainer>
</Modal.Content>
<Modal.Controls setVisible={setVisible}></Modal.Controls>
</Modal>
+128
View File
@@ -0,0 +1,128 @@
import React from "react";
import { Button } from "src/components/Button";
import styled from "styled-components";
const StyledSectionBody = styled.div`
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr;
gap: 50px;
align-items: center;
justify-content: space-between;
background: rgba(181, 116, 214, 0.23);
width: 80%;
margin: 5% auto 0;
border-radius: 6px;
padding: 50px;
@media only screen and (max-width: 768px) {
grid-template-columns: 1fr;
grid-template-rows: 1fr 1fr;
padding: 20px;
}
`;
const StyledPricingCard = styled.div<{ premium?: boolean }>`
padding: 6px;
width: 100%;
height: 100%;
${({ premium }) =>
premium
? `
background: rgba(255, 5, 214, 0.19);
border-radius: 4px;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
border: 1px solid rgba(255, 5, 214, 0.74);`
: `background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.3);`};
`;
const StyledPricingCardTitle = styled.h2`
text-align: center;
font-weight: 800;
font-size: 24px;
`;
const StyledPricingCardPrice = styled.h3`
text-align: center;
font-weight: 600;
font-size: 24px;
color: ${({ theme }) => theme.SILVER};
`;
const StyledPricingCardDetails = styled.ul`
color: ${({ theme }) => theme.TEXT_NORMAL};
line-height: 2.3;
padding: 20px;
`;
const StyledPricingCardDetailsItem = styled.li`
font-weight: 500;
@media only screen and (max-width: 768px) {
font-size: 14px;
}
`;
const StyledButton = styled(Button)`
border: 1px solid white;
`;
const StyledPricingSection = styled.section`
margin: 0 auto;
h1 {
text-align: center;
padding-bottom: 25px;
}
`;
export const PricingCards = () => {
return (
<StyledPricingSection>
<h1>Unlock Full Potential of JSON Crack</h1>
<StyledSectionBody>
<StyledPricingCard>
<StyledPricingCardTitle>Free</StyledPricingCardTitle>
<StyledPricingCardDetails>
<StyledPricingCardDetailsItem>Store up to 15 files</StyledPricingCardDetailsItem>
<StyledPricingCardDetailsItem>
Create short-links for saved JSON files
</StyledPricingCardDetailsItem>
<StyledPricingCardDetailsItem>Embed saved JSON instantly</StyledPricingCardDetailsItem>
</StyledPricingCardDetails>
</StyledPricingCard>
<StyledPricingCard premium>
<StyledPricingCardTitle>Premium</StyledPricingCardTitle>
<StyledPricingCardPrice>$5/mo</StyledPricingCardPrice>
<StyledPricingCardDetails>
<StyledPricingCardDetailsItem>
Create and share up to 200 files
</StyledPricingCardDetailsItem>
<StyledPricingCardDetailsItem>Store private JSON</StyledPricingCardDetailsItem>
<StyledPricingCardDetailsItem>
Get access to JSON Crack API to generate JSON remotely
</StyledPricingCardDetailsItem>
<StyledPricingCardDetailsItem>Everything in previous tier</StyledPricingCardDetailsItem>
</StyledPricingCardDetails>
<StyledButton
href="https://www.patreon.com/jsoncrack"
target="_blank"
status="SUCCESS"
block
link
>
GET IT NOW!
</StyledButton>
</StyledPricingCard>
</StyledSectionBody>
</StyledPricingSection>
);
};
-60
View File
@@ -1,60 +0,0 @@
import { ReactZoomPanPinchRef } from "react-zoom-pan-pinch";
import { CanvasDirection } from "reaflow";
import { defaultJson } from "src/constants/data";
import create from "zustand";
interface ConfigActions {
setJson: (json: string) => void;
setConfig: (key: keyof Config, value: unknown) => void;
getJson: () => string;
zoomIn: () => void;
zoomOut: () => void;
centerView: () => void;
}
const initialStates = {
json: defaultJson,
cursorMode: "move" as "move" | "navigation",
layout: "RIGHT" as CanvasDirection,
expand: true,
hideEditor: false,
performanceMode: true,
disableLoading: false,
zoomPanPinch: undefined as ReactZoomPanPinchRef | undefined,
};
export type Config = typeof initialStates;
const useConfig = create<Config & ConfigActions>()((set, get) => ({
...initialStates,
getJson: () => get().json,
setJson: (json: string) => set({ json }),
zoomIn: () => {
const zoomPanPinch = get().zoomPanPinch;
if (zoomPanPinch) {
zoomPanPinch.setTransform(
zoomPanPinch?.state.positionX,
zoomPanPinch?.state.positionY,
zoomPanPinch?.state.scale + 0.4
);
}
},
zoomOut: () => {
const zoomPanPinch = get().zoomPanPinch;
if (zoomPanPinch) {
zoomPanPinch.setTransform(
zoomPanPinch?.state.positionX,
zoomPanPinch?.state.positionY,
zoomPanPinch?.state.scale - 0.4
);
}
},
centerView: () => {
const zoomPanPinch = get().zoomPanPinch;
const canvas = document.querySelector(".jsoncrack-canvas") as HTMLElement;
if (zoomPanPinch && canvas) zoomPanPinch.zoomToElement(canvas);
},
setConfig: (setting: keyof Config, value: unknown) => set({ [setting]: value }),
}));
export default useConfig;
+11 -15
View File
@@ -1,14 +1,10 @@
import React from "react";
import {
searchQuery,
cleanupHighlight,
highlightMatchedNodes,
} from "src/utils/search";
import useConfig from "./store/useConfig";
import useGraph from "src/store/useGraph";
import { searchQuery, cleanupHighlight, highlightMatchedNodes } from "src/utils/search";
export const useFocusNode = () => {
const setConfig = useConfig(state => state.setConfig);
const zoomPanPinch = useConfig(state => state.zoomPanPinch);
const togglePerfMode = useGraph(state => state.togglePerfMode);
const zoomPanPinch = useGraph(state => state.zoomPanPinch);
const [selectedNode, setSelectedNode] = React.useState(0);
const [content, setContent] = React.useState({
value: "",
@@ -18,14 +14,14 @@ export const useFocusNode = () => {
const skip = () => setSelectedNode(current => current + 1);
React.useEffect(() => {
setConfig("performanceMode", !content.value.length);
togglePerfMode(!content.value.length);
const debouncer = setTimeout(() => {
setContent(val => ({ ...val, debounced: content.value }));
}, 800);
return () => clearTimeout(debouncer);
}, [content.value, setConfig]);
}, [content.value, togglePerfMode]);
React.useEffect(() => {
if (!zoomPanPinch) return;
@@ -39,18 +35,18 @@ export const useFocusNode = () => {
cleanupHighlight();
if (ref && matchedNode && matchedNode.parentElement) {
const newScale = 1;
const newScale = 0.4;
const x = Number(matchedNode.getAttribute("data-x"));
const y = Number(matchedNode.getAttribute("data-y"));
const newPositionX =
(ref.offsetLeft - x) * newScale +
ref.clientWidth / 2 -
matchedNode.getBoundingClientRect().width / 2;
ref.clientWidth / 10 -
matchedNode.getBoundingClientRect().width / 10;
const newPositionY =
(ref.offsetLeft - y) * newScale +
ref.clientHeight / 2 -
matchedNode.getBoundingClientRect().height / 2;
ref.clientHeight / 10 -
matchedNode.getBoundingClientRect().height / 10;
highlightMatchedNodes(matchedNodes, selectedNode);
+33
View File
@@ -0,0 +1,33 @@
import React from "react";
import useGraph from "src/store/useGraph";
const useHideNodes = () => {
const collapsedNodes = useGraph(state => state.collapsedNodes);
const collapsedEdges = useGraph(state => state.collapsedEdges);
const nodeList = React.useMemo(
() => collapsedNodes.map(id => `[id$="node-${id}"]`),
[collapsedNodes]
);
const edgeList = React.useMemo(
() => collapsedEdges.map(id => `[class$="edge-${id}"]`),
[collapsedEdges]
);
const checkNodes = () => {
const hiddenItems = document.querySelectorAll(".hide");
hiddenItems.forEach(item => item.classList.remove("hide"));
if (nodeList.length > 1) {
const selectedNodes = document.querySelectorAll(nodeList.join(","));
const selectedEdges = document.querySelectorAll(edgeList.join(","));
selectedNodes.forEach(node => node.classList.add("hide"));
selectedEdges.forEach(edge => edge.classList.add("hide"));
}
};
return { checkNodes };
};
export default useHideNodes;
-25
View File
@@ -1,25 +0,0 @@
import React from "react";
import styled from "styled-components";
const StyledPageWrapper = styled.iframe`
height: 100vh;
width: 100%;
border: none;
`;
const Embed = () => {
return (
<StyledPageWrapper
scrolling="no"
title="Untitled"
src="https://codepen.io/AykutSarac/embed/PoawZYo?default-tab=html%2Cresult"
loading="lazy"
>
See the Pen <a href="https://codepen.io/AykutSarac/pen/PoawZYo">Untitled</a> by
Aykut Saraç (<a href="https://codepen.io/AykutSarac">@AykutSarac</a>) on{" "}
<a href="https://codepen.io">CodePen</a>.
</StyledPageWrapper>
);
};
export default Embed;
-131
View File
@@ -1,131 +0,0 @@
import React from "react";
import dynamic from "next/dynamic";
import { useRouter } from "next/router";
import { parse } from "jsonc-parser";
import toast from "react-hot-toast";
import { baseURL, defaultJson } from "src/constants/data";
import { NodeModal } from "src/containers/Modals/NodeModal";
import useConfig from "src/hooks/store/useConfig";
import useGraph from "src/hooks/store/useGraph";
import { parser } from "src/utils/jsonParser";
import styled from "styled-components";
const Graph = dynamic<any>(() => import("src/components/Graph").then(c => c.Graph), {
ssr: false,
});
const StyledAttribute = styled.a`
position: fixed;
bottom: 0;
right: 0;
color: ${({ theme }) => theme.INTERACTIVE_NORMAL};
background: ${({ theme }) => theme.SILVER_DARK};
padding: 4px 8px;
font-size: 14px;
font-weight: 500;
border-radius: 3px 0 0 0;
opacity: 0.8;
@media only screen and (max-width: 768px) {
font-size: 12px;
}
`;
function inIframe() {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
interface EmbedMessage {
data: {
json?: string;
options?: any;
};
}
const WidgetPage = () => {
const { query, push } = useRouter();
const [json, setJson] = React.useState(defaultJson);
React.useEffect(() => {
if (query.json)
setJson(
JSON.stringify({
warning: "⚠️ Query params are deprecated now",
new: "Check out https://jsoncrack.com/embed",
})
);
}, [query.json]);
const [isModalVisible, setModalVisible] = React.useState(false);
const [selectedNode, setSelectedNode] = React.useState<[string, string][]>([]);
const collapsedNodes = useGraph(state => state.collapsedNodes);
const collapsedEdges = useGraph(state => state.collapsedEdges);
const loading = useGraph(state => state.loading);
const setGraphValue = useGraph(state => state.setGraphValue);
const centerView = useConfig(state => state.centerView);
const openModal = React.useCallback(() => setModalVisible(true), []);
React.useEffect(() => {
const nodeList = collapsedNodes.map(id => `[id$="node-${id}"]`);
const edgeList = collapsedEdges.map(id => `[class$="edge-${id}"]`);
const hiddenItems = document.querySelectorAll(".hide");
hiddenItems.forEach(item => item.classList.remove("hide"));
if (nodeList.length) {
const selectedNodes = document.querySelectorAll(nodeList.join(","));
const selectedEdges = document.querySelectorAll(edgeList.join(","));
selectedNodes.forEach(node => node.classList.add("hide"));
selectedEdges.forEach(edge => edge.classList.add("hide"));
}
}, [collapsedNodes, collapsedEdges, loading]);
React.useEffect(() => {
const handler = (event: EmbedMessage) => {
try {
if (!event.data?.json) return;
const parsedJson = JSON.stringify(parse(event.data.json));
setJson(parsedJson);
} catch (error) {
toast.error("Invalid JSON!");
}
};
window.addEventListener("message", handler);
return () => window.removeEventListener("message", handler);
}, []);
React.useEffect(() => {
if (json) {
const { nodes, edges } = parser(json);
setGraphValue("nodes", nodes);
setGraphValue("edges", edges);
}
if (!inIframe()) push("/");
}, [push, json, setGraphValue, centerView]);
return (
<>
<Graph openModal={openModal} setSelectedNode={setSelectedNode} isWidget />
<NodeModal
selectedNode={selectedNode}
visible={isModalVisible}
closeModal={() => setModalVisible(false)}
/>
<StyledAttribute href={`${baseURL}/editor`} target="_blank" rel="noreferrer">
jsoncrack.com
</StyledAttribute>
</>
);
};
export default WidgetPage;
+40 -44
View File
@@ -1,68 +1,64 @@
import React from "react";
import type { AppProps } from "next/app";
import { useRouter } from "next/router";
import { init } from "@sentry/nextjs";
import { decompress } from "compress-json";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Toaster } from "react-hot-toast";
import { GoogleAnalytics } from "src/components/GoogleAnalytics";
import { SupportButton } from "src/components/SupportButton";
import GlobalStyle from "src/constants/globalStyle";
import { darkTheme, lightTheme } from "src/constants/theme";
import useConfig from "src/hooks/store/useConfig";
import useStored from "src/hooks/store/useStored";
import { isValidJson } from "src/utils/isValidJson";
import { ModalController } from "src/containers/ModalController";
import useStored from "src/store/useStored";
import { ThemeProvider } from "styled-components";
if (process.env.NODE_ENV !== "development") {
init({
dsn: "https://d3345591295d4dd1b8c579b62003d939@o1284435.ingest.sentry.io/6495191",
tracesSampleRate: 0.5,
tracesSampleRate: 0.25,
});
}
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
retry: false,
},
},
});
function JsonCrack({ Component, pageProps }: AppProps) {
const { query } = useRouter();
const [isReady, setReady] = React.useState(false);
const lightmode = useStored(state => state.lightmode);
const setJson = useConfig(state => state.setJson);
const [isRendered, setRendered] = React.useState(false);
React.useEffect(() => {
const isJsonValid =
typeof query.json === "string" && isValidJson(decodeURIComponent(query.json));
if (isJsonValid) {
const jsonDecoded = decompress(JSON.parse(isJsonValid));
const jsonString = JSON.stringify(jsonDecoded);
setJson(jsonString);
}
}, [query.json, setJson]);
React.useEffect(() => {
setRendered(true);
setReady(true);
}, []);
if (!isRendered) return null;
return (
<>
<GoogleAnalytics />
<ThemeProvider theme={lightmode ? lightTheme : darkTheme}>
<GlobalStyle />
<Component {...pageProps} />
<Toaster
position="bottom-right"
toastOptions={{
style: {
background: "#4D4D4D",
color: "#B9BBBE",
},
}}
/>
<SupportButton />
</ThemeProvider>
</>
);
if (isReady)
return (
<QueryClientProvider client={queryClient}>
<GoogleAnalytics />
<ThemeProvider theme={lightmode ? lightTheme : darkTheme}>
<GlobalStyle />
<Component {...pageProps} />
<Toaster
position="top-right"
containerStyle={{
top: 40,
right: 6,
fontSize: 14,
}}
toastOptions={{
style: {
background: "#4D4D4D",
color: "#B9BBBE",
},
}}
/>
<ModalController />
</ThemeProvider>
</QueryClientProvider>
);
}
export default JsonCrack;
+2 -6
View File
@@ -15,13 +15,9 @@ class MyDocument extends Document {
<link rel="manifest" href="/manifest.json" />
<link rel="icon" href="/favicon.ico" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin="anonymous"
/>
<link
href="https://fonts.googleapis.com/css2?family=Catamaran:wght@400;500;700&family=Roboto+Mono:wght@500&family=Roboto:wght@400;500;700&display=swap"
href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@500&family=Roboto:wght@400;500;700&display=swap"
rel="stylesheet"
crossOrigin="anonymous"
/>
+1 -1
View File
@@ -15,7 +15,7 @@ const StyledNotFound = styled.div`
const StyledMessage = styled.h4`
color: ${({ theme }) => theme.FULL_WHITE};
font-size: 25px;
font-weight: 600;
font-weight: 800;
margin: 10px 0;
`;
+186
View File
@@ -0,0 +1,186 @@
import React from "react";
import dynamic from "next/dynamic";
import Head from "next/head";
import { materialDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
import { Button } from "src/components/Button";
import { Footer } from "src/components/Footer";
import styled from "styled-components";
const SyntaxHighlighter = dynamic(
() => import("react-syntax-highlighter").then(c => c.PrismAsync),
{
ssr: false,
}
);
const StyledFrame = styled.iframe`
border: none;
width: 100%;
height: 400px;
flex: 1;
`;
const StyledPage = styled.div`
padding: 5%;
`;
const StyledContent = styled.section`
margin-top: 20px;
background: rgba(181, 116, 214, 0.23);
padding: 16px;
border-radius: 6px;
`;
const StyledDescription = styled.div``;
const StyledContentBody = styled.div`
display: flex;
flex-wrap: wrap;
gap: 15px;
line-height: 1.8;
${StyledDescription} {
flex: 1;
}
`;
const StyledHighlight = styled.span<{ link?: boolean; alert?: boolean }>`
text-align: left;
white-space: nowrap;
color: ${({ theme, link, alert }) =>
alert ? theme.DANGER : link ? theme.BLURPLE : theme.TEXT_POSITIVE};
background: ${({ theme }) => theme.BACKGROUND_TERTIARY};
border-radius: 4px;
font-weight: 500;
padding: 4px;
font-size: 14px;
margin: ${({ alert }) => alert && "8px 0"};
`;
const Docs = () => {
return (
<>
<Head>
<title>Creating JSON Crack Embed | JSON Crack</title>
<meta name="description" content="Embedding JSON Crack tutorial into your websites." />
</Head>
<StyledPage>
<Button href="/" link status="SECONDARY">
&lt; Go Back
</Button>
<h1>Documentation</h1>
<StyledContent>
<h2># Fetching from URL</h2>
<StyledContentBody>
<StyledDescription>
By adding <StyledHighlight>?json=https://catfact.ninja/fact</StyledHighlight> query at
the end of iframe src you will be able to fetch from URL at widgets without additional
scripts. This applies to editor page as well, the following link will fetch the url at
the editor:{" "}
<StyledHighlight
as="a"
href="https://jsoncrack.com/editor?json=https://catfact.ninja/fact"
link
>
https://jsoncrack.com/editor?json=https://catfact.ninja/fact
</StyledHighlight>
</StyledDescription>
<StyledFrame
scrolling="no"
title="Untitled"
src="https://codepen.io/AykutSarac/embed/KKBpWVR?default-tab=html%2Cresult"
loading="eager"
>
See the Pen <a href="https://codepen.io/AykutSarac/pen/KKBpWVR">Untitled</a> by Aykut
Saraç (<a href="https://codepen.io/AykutSarac">@AykutSarac</a>) on{" "}
<a href="https://codepen.io">CodePen</a>.
</StyledFrame>
</StyledContentBody>
</StyledContent>
<StyledContent>
<h2># Embed Saved JSON</h2>
<StyledContentBody>
<StyledDescription>
Just like fetching from URL above, you can embed saved public json by adding the json
id to &quot;json&quot; query{" "}
<StyledHighlight>?json=639b65c5a82efc29a24b2de2</StyledHighlight>
</StyledDescription>
<StyledFrame
scrolling="no"
title="Untitled"
src="https://codepen.io/AykutSarac/embed/vYaORgM?default-tab=html%2Cresult"
loading="lazy"
>
See the Pen <a href="https://codepen.io/AykutSarac/pen/vYaORgM">Untitled</a> by Aykut
Saraç (<a href="https://codepen.io/AykutSarac">@AykutSarac</a>) on{" "}
<a href="https://codepen.io">CodePen</a>.
</StyledFrame>
</StyledContentBody>
</StyledContent>
<StyledContent>
<h2># Communicating with API</h2>
<h3>◼︎ Post Message to Embed</h3>
<StyledContentBody>
<StyledDescription>
Communicating with the embed is possible with{" "}
<StyledHighlight
as="a"
href="https://developer.mozilla.org/en-US/docs/Web/API/MessagePort/postMessage"
link
>
MessagePort
</StyledHighlight>
, you should pass an object consist of &quot;json&quot; and &quot;options&quot; key
where json is a string and options is an object that may contain the following:
<SyntaxHighlighter language="markdown" style={materialDark} showLineNumbers={true}>
{`{\n\ttheme: "light" | "dark",\n\tdirection: "TOP" | "RIGHT" | "DOWN" | "LEFT"\n}`}
</SyntaxHighlighter>
</StyledDescription>
<StyledFrame
scrolling="no"
title="Untitled"
src="https://codepen.io/AykutSarac/embed/rNrVyWP?default-tab=html%2Cresult"
loading="lazy"
>
See the Pen <a href="https://codepen.io/AykutSarac/pen/rNrVyWP">Untitled</a> by Aykut
Saraç (<a href="https://codepen.io/AykutSarac">@AykutSarac</a>) on{" "}
<a href="https://codepen.io">CodePen</a>.
</StyledFrame>
</StyledContentBody>
</StyledContent>
<StyledContent>
<h3>◼︎ On Page Load</h3>
<StyledContentBody>
<StyledDescription>
<StyledHighlight as="div" alert>
⚠️ <b>Important!</b> - iframe should be defined before the script tag
</StyledHighlight>
<StyledHighlight as="div" alert>
⚠️ <b>Note</b> - postMessage should be delayed using setTimeout
</StyledHighlight>
To display JSON on load event, you should post json into iframe using it&apos;s onload
event like in the example. Make sure to use{" "}
<StyledHighlight>setTimeout</StyledHighlight> when loading data and set a time around
500ms otherwise it won&apos;t work.
</StyledDescription>
<StyledFrame
scrolling="no"
title="Untitled"
src="https://codepen.io/AykutSarac/embed/QWBbpqx?default-tab=html%2Cresult"
loading="lazy"
>
See the Pen <a href="https://codepen.io/AykutSarac/pen/QWBbpqx">Untitled</a> by Aykut
Saraç (<a href="https://codepen.io/AykutSarac">@AykutSarac</a>) on{" "}
<a href="https://codepen.io">CodePen</a>.
</StyledFrame>
</StyledContentBody>
</StyledContent>
</StyledPage>
<Footer />
</>
);
};
export default Docs;
@@ -1,13 +1,18 @@
import React from "react";
import Head from "next/head";
import { useRouter } from "next/router";
import { Loading } from "src/components/Loading";
import { Sidebar } from "src/components/Sidebar";
import { BottomBar } from "src/containers/Editor/BottomBar";
import Panes from "src/containers/Editor/Panes";
import useJson from "src/store/useJson";
import useUser from "src/store/useUser";
import styled from "styled-components";
export const StyledPageWrapper = styled.div`
display: flex;
flex-direction: row;
height: 100vh;
height: calc(100vh - 28px);
width: 100%;
@media only screen and (max-width: 768px) {
@@ -24,14 +29,27 @@ export const StyledEditorWrapper = styled.div`
`;
const EditorPage: React.FC = () => {
const { isReady, query } = useRouter();
const checkSession = useUser(state => state.checkSession);
const fetchJson = useJson(state => state.fetchJson);
const loading = useJson(state => state.loading);
React.useEffect(() => {
// Fetch JSON by query
// Check Session User
if (isReady) {
checkSession();
fetchJson(query.json);
}
}, [checkSession, fetchJson, isReady, query.json]);
if (loading) return <Loading message="Fetching JSON from cloud..." />;
return (
<StyledEditorWrapper>
<Head>
<title>Editor | JSON Crack</title>
<meta
name="description"
content="View your JSON data in graphs instantly."
/>
<meta name="description" content="View your JSON data in graphs instantly." />
</Head>
<StyledPageWrapper>
<Sidebar />
@@ -39,6 +57,7 @@ const EditorPage: React.FC = () => {
<Panes />
</StyledEditorWrapper>
</StyledPageWrapper>
<BottomBar />
</StyledEditorWrapper>
);
};
+35
View File
@@ -0,0 +1,35 @@
import React from "react";
import { Button } from "src/components/Button";
import { Footer } from "src/components/Footer";
import { PricingCards } from "src/containers/PricingCards";
import styled from "styled-components";
const StyledPageWrapper = styled.div`
padding: 5%;
`;
const StyledHeroSection = styled.section`
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
`;
const Pricing = () => {
return (
<>
<StyledPageWrapper>
<Button href="/" link>
&lt; Go Back
</Button>
<StyledHeroSection>
<img src="assets/icon.png" alt="json crack" width="400" />
<h1>Premium</h1>
</StyledHeroSection>
<PricingCards />
</StyledPageWrapper>
<Footer />
</>
);
};
export default Pricing;
+92
View File
@@ -0,0 +1,92 @@
import React from "react";
import dynamic from "next/dynamic";
import { useRouter } from "next/router";
import toast from "react-hot-toast";
import { baseURL } from "src/constants/data";
import { darkTheme, lightTheme } from "src/constants/theme";
import useGraph from "src/store/useGraph";
import useJson from "src/store/useJson";
import styled, { ThemeProvider } from "styled-components";
const GraphCanvas = dynamic(
() => import("src/containers/Editor/LiveEditor/GraphCanvas").then(c => c.GraphCanvas),
{
ssr: false,
}
);
const StyledAttribute = styled.a`
position: fixed;
bottom: 0;
right: 0;
color: ${({ theme }) => theme.INTERACTIVE_NORMAL};
background: ${({ theme }) => theme.SILVER_DARK};
padding: 4px 8px;
font-size: 14px;
font-weight: 500;
border-radius: 3px 0 0 0;
opacity: 0.8;
@media only screen and (max-width: 768px) {
font-size: 12px;
}
`;
function inIframe() {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
interface EmbedMessage {
data: {
json?: string;
options?: any;
};
}
const WidgetPage = () => {
const { query, push, isReady } = useRouter();
const [theme, setTheme] = React.useState("dark");
const fetchJson = useJson(state => state.fetchJson);
const setGraph = useGraph(state => state.setGraph);
React.useEffect(() => {
if (isReady) {
fetchJson(query.json);
if (!inIframe()) push("/");
}
}, [fetchJson, isReady, push, query.json]);
React.useEffect(() => {
const handler = (event: EmbedMessage) => {
try {
if (!event.data?.json) return;
if (event.data?.options?.theme === "light" || event.data?.options?.theme === "dark") {
setTheme(event.data.options.theme);
}
setGraph(event.data.json, event.data.options);
} catch (error) {
console.error(error);
toast.error("Invalid JSON!");
}
};
window.addEventListener("message", handler);
return () => window.removeEventListener("message", handler);
}, [setGraph, theme]);
return (
<ThemeProvider theme={theme === "dark" ? darkTheme : lightTheme}>
<GraphCanvas isWidget />
<StyledAttribute href={`${baseURL}/editor`} target="_blank" rel="noreferrer">
jsoncrack.com
</StyledAttribute>
</ThemeProvider>
);
};
export default WidgetPage;
+35
View File
@@ -0,0 +1,35 @@
import { compressToBase64 } from "lz-string";
import { altogic, AltogicResponse } from "src/api/altogic";
import { Json } from "src/typings/altogic";
const saveJson = async ({
id,
data,
}: {
id?: string | null;
data: string;
}): Promise<AltogicResponse<{ _id: string }>> => {
const compressedData = compressToBase64(data);
if (id) {
return await altogic.endpoint.put(`json/${id}`, {
json: compressedData,
});
}
return await altogic.endpoint.post("json", {
json: compressedData,
});
};
const getAllJson = async (): Promise<AltogicResponse<{ result: Json[] }>> =>
await altogic.endpoint.get(`json`);
const updateJson = async (id: string, data: object) =>
await altogic.endpoint.put(`json/${id}`, {
...data,
});
const deleteJson = async (id: string) => await altogic.endpoint.delete(`json/${id}`);
export { saveJson, getAllJson, updateJson, deleteJson };
@@ -1,11 +1,20 @@
import { ReactZoomPanPinchRef } from "react-zoom-pan-pinch";
import { CanvasDirection } from "reaflow";
import { Graph } from "src/components/Graph";
import { getChildrenEdges } from "src/utils/getChildrenEdges";
import { getOutgoers } from "src/utils/getOutgoers";
import { parser } from "src/utils/jsonParser";
import create from "zustand";
import useJson from "./useJson";
const initialStates = {
loading: false,
zoomPanPinch: undefined as ReactZoomPanPinchRef | undefined,
direction: "RIGHT" as CanvasDirection,
loading: true,
graphCollapsed: false,
foldNodes: false,
fullscreen: false,
performanceMode: true,
nodes: [] as NodeData[],
edges: [] as EdgeData[],
collapsedNodes: [] as string[],
@@ -16,23 +25,39 @@ const initialStates = {
export type Graph = typeof initialStates;
interface GraphActions {
setGraphValue: (key: keyof Graph, value: any) => void;
setGraph: (json?: string, options?: Partial<Graph>[]) => void;
setLoading: (loading: boolean) => void;
setDirection: (direction: CanvasDirection) => void;
setZoomPanPinch: (ref: ReactZoomPanPinchRef) => void;
expandNodes: (nodeId: string) => void;
collapseNodes: (nodeId: string) => void;
collapseGraph: () => void;
expandGraph: () => void;
toggleFold: (value: boolean) => void;
toggleFullscreen: (value: boolean) => void;
togglePerfMode: (value: boolean) => void;
zoomIn: () => void;
zoomOut: () => void;
centerView: () => void;
}
const useGraph = create<Graph & GraphActions>((set, get) => ({
...initialStates,
setGraphValue: (key, value) =>
setGraph: (data, options) => {
const { nodes, edges } = parser(data ?? useJson.getState().json);
set({
nodes,
edges,
collapsedParents: [],
collapsedNodes: [],
collapsedEdges: [],
[key]: value,
}),
graphCollapsed: false,
loading: true,
...options,
});
},
setDirection: direction => set({ direction }),
setLoading: loading => set({ loading }),
expandNodes: nodeId => {
const [childrenNodes, matchingNodes] = getOutgoers(
@@ -43,29 +68,26 @@ const useGraph = create<Graph & GraphActions>((set, get) => ({
);
const childrenEdges = getChildrenEdges(childrenNodes, get().edges);
let nodesConnectedToParent = childrenEdges.reduce((nodes: string[], edge) => {
edge.from && !nodes.includes(edge.from) && nodes.push(edge.from)
edge.to && !nodes.includes(edge.to) && nodes.push(edge.to)
return nodes
edge.from && !nodes.includes(edge.from) && nodes.push(edge.from);
edge.to && !nodes.includes(edge.to) && nodes.push(edge.to);
return nodes;
}, []);
const matchingNodesConnectedToParent = matchingNodes.filter(node => nodesConnectedToParent.includes(node));
const matchingNodesConnectedToParent = matchingNodes.filter(node =>
nodesConnectedToParent.includes(node)
);
const nodeIds = childrenNodes.map(node => node.id).concat(matchingNodesConnectedToParent);
const edgeIds = childrenEdges.map(edge => edge.id);
const collapsedParents = get().collapsedParents.filter(cp => cp !== nodeId);
const collapsedNodes = get().collapsedNodes.filter(
nodeId => !nodeIds.includes(nodeId)
);
const collapsedEdges = get().collapsedEdges.filter(
edgeId => !edgeIds.includes(edgeId)
);
const collapsedNodes = get().collapsedNodes.filter(nodeId => !nodeIds.includes(nodeId));
const collapsedEdges = get().collapsedEdges.filter(edgeId => !edgeIds.includes(edgeId));
set({
collapsedParents,
collapsedNodes,
collapsedEdges,
graphCollapsed: false,
graphCollapsed: !!collapsedNodes.length,
});
},
collapseNodes: nodeId => {
@@ -79,6 +101,7 @@ const useGraph = create<Graph & GraphActions>((set, get) => ({
collapsedParents: get().collapsedParents.concat(nodeId),
collapsedNodes: get().collapsedNodes.concat(nodeIds),
collapsedEdges: get().collapsedEdges.concat(edgeIds),
graphCollapsed: !!get().collapsedNodes.concat(nodeIds).length,
});
},
collapseGraph: () => {
@@ -90,19 +113,19 @@ const useGraph = create<Graph & GraphActions>((set, get) => ({
.filter(edge => parentNodesIds.includes(edge.from))
.map(edge => edge.to);
const collapsedParents = get()
.nodes.filter(node => !parentNodesIds.includes(node.id) && node.data.parent)
.map(node => node.id);
const collapsedNodes = get()
.nodes.filter(
node => !parentNodesIds.includes(node.id) && !secondDegreeNodesIds.includes(node.id)
)
.map(node => node.id);
set({
collapsedParents: get()
.nodes.filter(
node => !parentNodesIds.includes(node.id) && node.data.isParent
)
.map(node => node.id),
collapsedNodes: get()
.nodes.filter(
node =>
!parentNodesIds.includes(node.id) &&
!secondDegreeNodesIds.includes(node.id)
)
.map(node => node.id),
collapsedParents,
collapsedNodes,
collapsedEdges: get()
.edges.filter(edge => !parentNodesIds.includes(edge.from))
.map(edge => edge.id),
@@ -117,6 +140,39 @@ const useGraph = create<Graph & GraphActions>((set, get) => ({
graphCollapsed: false,
});
},
zoomIn: () => {
const zoomPanPinch = get().zoomPanPinch;
if (zoomPanPinch) {
zoomPanPinch.setTransform(
zoomPanPinch?.state.positionX,
zoomPanPinch?.state.positionY,
zoomPanPinch?.state.scale + 0.4
);
}
},
zoomOut: () => {
const zoomPanPinch = get().zoomPanPinch;
if (zoomPanPinch) {
zoomPanPinch.setTransform(
zoomPanPinch?.state.positionX,
zoomPanPinch?.state.positionY,
zoomPanPinch?.state.scale - 0.4
);
}
},
centerView: () => {
const zoomPanPinch = get().zoomPanPinch;
const canvas = document.querySelector(".jsoncrack-canvas") as HTMLElement;
if (zoomPanPinch && canvas) zoomPanPinch.zoomToElement(canvas);
},
toggleFold: foldNodes => {
set({ foldNodes });
get().setGraph();
},
togglePerfMode: performanceMode => set({ performanceMode }),
toggleFullscreen: fullscreen => set({ fullscreen }),
setZoomPanPinch: zoomPanPinch => set({ zoomPanPinch }),
}));
export default useGraph;
+105
View File
@@ -0,0 +1,105 @@
import { decompressFromBase64 } from "lz-string";
import toast from "react-hot-toast";
import { altogic } from "src/api/altogic";
import { defaultJson } from "src/constants/data";
import { saveJson as saveJsonDB } from "src/services/db/json";
import useGraph from "src/store/useGraph";
import { Json } from "src/typings/altogic";
import create from "zustand";
interface JsonActions {
setJson: (json: string) => void;
getJson: () => string;
getHasChanges: () => boolean;
fetchJson: (jsonId: string | string[] | undefined) => void;
setError: (hasError: boolean) => void;
setHasChanges: (hasChanges: boolean) => void;
saveJson: (isNew?: boolean) => Promise<string | undefined>;
}
const initialStates = {
data: null as Json | null,
json: "",
loading: true,
hasChanges: false,
hasError: false,
};
export type JsonStates = typeof initialStates;
const useJson = create<JsonStates & JsonActions>()((set, get) => ({
...initialStates,
getJson: () => get().json,
getHasChanges: () => get().hasChanges,
fetchJson: async jsonId => {
const isURL = new RegExp(
/^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)$/
);
if (typeof jsonId === "string" && isURL.test(jsonId)) {
try {
const res = await fetch(jsonId);
const json = await res.json();
const jsonStr = JSON.stringify(json, null, 2);
useGraph.getState().setGraph(jsonStr);
return set({ json: jsonStr, loading: false });
} catch (error) {
useGraph.getState().setGraph(defaultJson);
set({ json: defaultJson, loading: false });
toast.error("Failed to fetch JSON from URL!");
}
} else if (jsonId) {
const { data, errors } = await altogic.endpoint.get(`json/${jsonId}`, undefined, {
userid: altogic.auth.getUser()?._id,
});
if (!errors) {
const decompressedData = decompressFromBase64(data.json);
if (decompressedData) {
useGraph.getState().setGraph(decompressedData);
return set({
data,
json: decompressedData ?? undefined,
loading: false,
});
}
}
}
useGraph.getState().setGraph(defaultJson);
set({ json: defaultJson, loading: false });
},
setJson: json => {
useGraph.getState().setGraph(json);
set({ json, hasChanges: true });
},
saveJson: async (isNew = true) => {
try {
const url = new URL(window.location.href);
const params = new URLSearchParams(url.search);
const jsonQuery = params.get("json");
toast.loading("Saving JSON...", { id: "jsonSave" });
const res = await saveJsonDB({ id: isNew ? undefined : jsonQuery, data: get().json });
if (res.errors && res.errors.items.length > 0) throw res.errors;
toast.success("JSON saved to cloud", { id: "jsonSave" });
set({ hasChanges: false });
return res.data._id;
} catch (error: any) {
if (error?.items?.length > 0) {
toast.error(error.items[0].message, { id: "jsonSave", duration: 5000 });
return undefined;
}
toast.error("Failed to save JSON!", { id: "jsonSave" });
return undefined;
}
},
setError: (hasError: boolean) => set({ hasError }),
setHasChanges: (hasChanges: boolean) => set({ hasChanges }),
}));
export default useJson;
+38
View File
@@ -0,0 +1,38 @@
import create from "zustand";
import useUser from "./useUser";
interface ModalActions {
setVisible: (modal: keyof typeof initialStates) => (visible: boolean) => void;
}
const initialStates = {
clear: false,
cloud: false,
download: false,
goals: false,
import: false,
account: false,
node: false,
settings: false,
share: false,
login: false,
};
type ModalType = keyof typeof initialStates;
const authModals: ModalType[] = ["cloud", "share", "account"];
export type ModalStates = typeof initialStates;
const useModal = create<ModalStates & ModalActions>()(set => ({
...initialStates,
setVisible: modal => visible => {
if (authModals.includes(modal) && !useUser.getState().isAuthenticated) {
return set({ login: true });
}
set({ [modal]: visible });
},
}));
export default useModal;
@@ -1,5 +1,6 @@
import create from "zustand";
import { persist } from "zustand/middleware";
import useGraph from "./useGraph";
type Sponsor = {
handle: string;
@@ -15,30 +16,29 @@ function getTomorrow() {
return new Date(tomorrow).getTime();
}
export interface Config {
lightmode: boolean;
hideCollapse: boolean;
hideChildrenCount: boolean;
const initialStates = {
lightmode: false,
hideCollapse: true,
childrenCount: true,
imagePreview: true,
sponsors: {
users: Sponsor[];
nextDate: number;
};
users: [] as Sponsor[],
nextDate: Date.now(),
},
};
export interface ConfigActions {
setSponsors: (sponsors: Sponsor[]) => void;
setLightTheme: (theme: boolean) => void;
toggleHideCollapse: (value: boolean) => void;
toggleHideChildrenCount: (value: boolean) => void;
toggleChildrenCount: (value: boolean) => void;
toggleImagePreview: (value: boolean) => void;
}
const useStored = create(
persist<Config>(
persist<typeof initialStates & ConfigActions>(
set => ({
lightmode: false,
hideCollapse: false,
hideChildrenCount: true,
sponsors: {
users: [],
nextDate: Date.now(),
},
...initialStates,
setLightTheme: (value: boolean) =>
set({
lightmode: value,
@@ -51,7 +51,11 @@ const useStored = create(
},
}),
toggleHideCollapse: (value: boolean) => set({ hideCollapse: value }),
toggleHideChildrenCount: (value: boolean) => set({ hideChildrenCount: value }),
toggleChildrenCount: (value: boolean) => set({ childrenCount: value }),
toggleImagePreview: (value: boolean) => {
set({ imagePreview: value });
useGraph.getState().setGraph();
},
}),
{
name: "config",
+59
View File
@@ -0,0 +1,59 @@
import toast from "react-hot-toast";
import { altogic } from "src/api/altogic";
import { AltogicAuth, User } from "src/typings/altogic";
import create from "zustand";
import useModal from "./useModal";
interface UserActions {
login: (response: AltogicAuth) => void;
logout: () => void;
setUser: (key: keyof typeof initialStates, value: any) => void;
checkSession: () => void;
isPremium: () => boolean;
}
const initialStates = {
isAuthenticated: false,
user: null as User | null,
};
export type UserStates = typeof initialStates;
const useUser = create<UserStates & UserActions>()((set, get) => ({
...initialStates,
setUser: (key, value) => set({ [key]: value }),
isPremium: () => {
const user = get().user;
if (user) return user.type > 0;
return false;
},
logout: () => {
altogic.auth.signOut();
toast.success("Logged out.");
useModal.setState({ account: false });
set(initialStates);
},
login: response => {
set({ user: response.user as any, isAuthenticated: true });
},
checkSession: async () => {
const currentSession = altogic.auth.getSession();
if (currentSession) {
const dbUser = await altogic.auth.getUserFromDB();
altogic.auth.setSession(currentSession);
set({ user: dbUser.user as any, isAuthenticated: true });
} else {
if (!new URLSearchParams(window.location.search).get("access_token")) return;
const data = await altogic.auth.getAuthGrant();
if (!data.errors?.items.length) {
set({ user: data.user as any, isAuthenticated: true });
}
}
},
}));
export default useUser;
+56
View File
@@ -0,0 +1,56 @@
export interface User {
_id: string;
provider: string;
providerUserId: string;
email: string;
name: string;
profilePicture: string;
signUpAt: Date;
lastLoginAt: Date;
type: 0 | 1;
}
export interface Json {
_id: string;
createdAt: string;
updatedAt: string;
json: string;
name: string;
private: false;
}
interface Device {
family: string;
major: string;
minor: string;
patch: string;
}
interface Os {
family: string;
major: string;
minor: string;
patch: string;
}
interface UserAgent {
family: string;
major: string;
minor: string;
patch: string;
device: Device;
os: Os;
}
interface Session {
userId: string;
token: string;
creationDtm: Date;
userAgent: UserAgent;
accessGroupKeys: any[];
}
export interface AltogicAuth {
user: User;
session: Session;
}
+4 -34
View File
@@ -1,38 +1,8 @@
import theme from "src/constants/theme";
import "styled-components";
declare module "styled-components" {
export interface DefaultTheme {
BLURPLE: string;
FULL_WHITE: string;
BLACK: string;
BLACK_LIGHT: string;
BLACK_DARK: string;
BLACK_PRIMARY: string;
BLACK_SECONDARY: string;
CRIMSON: string;
DARK_SALMON: string;
DANGER: string;
LIGHTGREEN: string;
SEAGREEN: string;
ORANGE: string;
SILVER: string;
SILVER_DARK: string;
PRIMARY: string;
NODE_KEY: string;
OBJECT_KEY: string;
SIDEBAR_ICONS: string;
type CustomTheme = typeof theme;
INTERACTIVE_NORMAL: string;
INTERACTIVE_HOVER: string;
INTERACTIVE_ACTIVE: string;
BACKGROUND_NODE: string;
BACKGROUND_TERTIARY: string;
BACKGROUND_SECONDARY: string;
BACKGROUND_PRIMARY: string;
BACKGROUND_MODIFIER_ACCENT: string;
MODAL_BACKGROUND: string;
TEXT_NORMAL: string;
TEXT_POSITIVE: string;
TEXT_DANGER: string;
}
declare module "styled-components" {
export interface DefaultTheme extends CustomTheme {}
}
-6
View File
@@ -1,11 +1,5 @@
type CanvasDirection = "LEFT" | "RIGHT" | "DOWN" | "UP";
interface CustomNodeData {
isParent: true;
childrenCount: children.length;
children: NodeData[];
}
interface NodeData<T = any> {
id: string;
disabled?: boolean;
+2 -6
View File
@@ -1,11 +1,7 @@
export const getChildrenEdges = (
nodes: NodeData[],
edges: EdgeData[]
): EdgeData[] => {
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)
edge => nodeIds.includes(edge.from as string) || nodeIds.includes(edge.to as string)
);
};
@@ -1,5 +1,5 @@
export function getNextLayout(layout: "LEFT" | "RIGHT" | "DOWN" | "UP") {
switch (layout) {
export function getNextDirection(direction: "LEFT" | "RIGHT" | "DOWN" | "UP") {
switch (direction) {
case "RIGHT":
return "DOWN";
+6 -2
View File
@@ -7,11 +7,15 @@ export const getOutgoers = (
const outgoerNodes: NodeData[] = [];
const matchingNodes: string[] = [];
if (parent.includes(nodeId)) {
const initialParentNode = nodes.find(n => n.id === nodeId);
if (initialParentNode) outgoerNodes.push(initialParentNode);
}
const runner = (nodeId: string) => {
const outgoerIds = edges.filter(e => e.from === nodeId).map(e => e.to);
const nodeList = nodes.filter(n => {
if (parent.includes(n.id) && !matchingNodes.includes(n.id))
matchingNodes.push(n.id);
if (parent.includes(n.id) && !matchingNodes.includes(n.id)) matchingNodes.push(n.id);
return outgoerIds.includes(n.id) && !parent.includes(n.id);
});
-10
View File
@@ -1,10 +0,0 @@
import { parse } from "jsonc-parser";
export const isValidJson = (str: string) => {
try {
parse(str);
} catch (e) {
return false;
}
return str;
};
+297 -117
View File
@@ -1,144 +1,329 @@
import { parse } from "jsonc-parser";
import { Node, parseTree } from "jsonc-parser";
import useGraph from "src/store/useGraph";
import useStored from "src/store/useStored";
const calculateSize = (
text: string | [string, string][],
isParent = false,
isExpanded: boolean
) => {
let value = "";
const calculateSize = (text: string | [string, string][], isParent = false) => {
const isFolded = useGraph.getState().foldNodes;
const isImagePreview = useStored.getState().imagePreview;
let lineCounts = 1;
let lineLengths: number[] = [];
if (typeof text === "string") value = text;
else value = text.map(([k, v]) => `${k}: ${v}`).join("\n");
if (typeof text === "string") {
lineLengths.push(text.length);
} else {
lineCounts = text.map(([k, v]) => {
const length = `${k}: ${v}`.length;
const line = length > 150 ? 150 : length;
lineLengths.push(line);
return `${k}: ${v}`;
}).length;
}
const lineCount = value.split("\n");
const lineLengths = lineCount.map(line => line.length);
const longestLine = lineLengths.sort((a, b) => b - a)[0];
const longestLine = Math.max(...lineLengths);
const getWidth = () => {
if (text.length === 0) return 35;
if (Array.isArray(text) && !text.length) return 40;
if (isExpanded) return 35 + longestLine * 8 + (isParent ? 60 : 0);
if (isParent) return 170;
if (!isFolded) return 35 + longestLine * 7.8 + (isParent ? 60 : 0);
if (isParent && isFolded) return 170;
return 200;
};
const getHeight = () => {
if (lineCount.length * 17.8 < 30) return 40;
return (lineCount.length + 1) * 18;
if (lineCounts * 17.8 < 30) return 40;
return (lineCounts + 1) * 18;
};
const isImage =
!Array.isArray(text) && /(https?:\/\/.*\.(?:png|jpg|gif))/i.test(text) && isImagePreview;
return {
width: getWidth(),
height: getHeight(),
width: isImage ? 80 : getWidth(),
height: isImage ? 80 : getHeight(),
};
};
const filterChild = ([_, v]) => {
const isNull = v === null;
const isArray = Array.isArray(v) && v.length;
const isObject = v instanceof Object;
export const parser = (jsonStr: string) => {
try {
let json = parseTree(jsonStr);
let nodes: NodeData[] = [];
let edges: EdgeData[] = [];
return !isNull && (isArray || isObject);
};
const filterValues = ([k, v]) => {
if (Array.isArray(v) || v instanceof Object) return false;
return true;
};
function generateChildren(object: Object, isExpanded = true, nextId: () => string) {
if (!(object instanceof Object)) object = [object];
return Object.entries(object)
.filter(filterChild)
.flatMap(([key, v]) => {
const { width, height } = calculateSize(key, true, isExpanded);
const children = extract(v, isExpanded, nextId);
return [
const addNodes = (
text: any,
width: number,
height: number,
parent: "string" | "number" | "boolean" | "object" | "array" | "null" | false,
isEmpty?: boolean
) => {
let actualId = String(nodes.length + 1);
nodes = [
...nodes,
{
id: nextId(),
text: key,
children,
width,
height,
id: actualId,
text: text,
width: width,
height: height,
data: {
isParent: true,
childrenCount: children.length,
parent: parent === "array" || parent === "object" ? parent : false,
childrenCount: parent ? 1 : 0,
isEmpty: isEmpty,
},
},
];
});
}
function generateNodeData(object: Object) {
if (object instanceof Object) {
const entries = Object.entries(object).filter(filterValues);
return entries;
}
return String(object);
}
const extract = (
os: string[] | object[] | null,
isExpanded = true,
nextId = (
id => () =>
String(++id)
)(0)
) => {
if (!os) return [];
return [os].flat().map(o => {
const text = generateNodeData(o);
const { width, height } = calculateSize(text, false, isExpanded);
return {
id: nextId(),
text,
width,
height,
children: generateChildren(o, isExpanded, nextId),
data: {
isParent: false,
childrenCount: 0,
isEmpty: !text.length,
},
return actualId;
};
});
};
const flatten = (xs: { id: string; children: never[] }[]) =>
xs.flatMap(({ children, ...rest }) => [rest, ...flatten(children)]);
const addEdges = (from: string, to: string) => {
edges = [
...edges,
{
id: `e${from}-${to}`,
from: from,
to: to,
},
];
};
const relationships = (xs: { id: string; children: never[] }[]) => {
return xs.flatMap(({ id: from, children = [] }) => [
...children.map(({ id: to }) => ({
id: `e${from}-${to}`,
from,
to,
})),
...relationships(children),
]);
};
let parentName: string = "";
let bracketOpen: { id: string; type: string }[] = [];
let objectsFromArray: number[] = [];
let objectsFromArrayId = 0;
let notHaveParent: string[] = [];
let brothersNode: [string, string][] = [];
let brothersParentId: string | undefined = "";
let brotherKey: string = "";
let brothersNodeProps: {
id: string;
parentId: string | undefined;
objectsFromArrayId: number | undefined;
}[] = [];
export const parser = (jsonStr: string, isExpanded = true) => {
try {
let json = parse(jsonStr);
if (!Array.isArray(json)) json = [json];
const nodes: NodeData[] = [];
const edges: EdgeData[] = [];
const traverse = (
objectToTraverse: Node,
parentType?: string,
myParentId?: string,
nextType?: string
) => {
let { type, children, value } = objectToTraverse;
const mappedElements = extract(json, isExpanded);
const res = [...flatten(mappedElements), ...relationships(mappedElements)];
if (!children) {
if (value !== undefined) {
if (parentType === "property" && nextType !== "object" && nextType !== "array") {
brothersParentId = myParentId;
if (nextType === undefined) {
// add key and value to brothers node
brothersNode = [...brothersNode, [brotherKey, value]];
} else {
brotherKey = value;
}
} else if (parentType === "array") {
const { width, height } = calculateSize(String(value), false);
const nodeFromArrayId = addNodes(String(value), width, height, false);
if (myParentId) {
addEdges(myParentId, nodeFromArrayId);
}
}
if (nextType && parentType !== "array") {
if (nextType === "object" || nextType === "array") {
parentName = value;
}
}
}
} else if (children) {
let parentId: string | undefined;
res.forEach(data => {
if (isNode(data)) {
nodes.push(data);
} else {
edges.push(data);
if (type !== "property" && parentName !== "") {
// add last brothers node and add parent node
if (brothersNode.length > 0) {
// add or concat brothers node of same parent
let findBrothersNode = brothersNodeProps.find(
e =>
e.parentId === brothersParentId &&
e.objectsFromArrayId === objectsFromArray[objectsFromArray.length - 1]
);
if (findBrothersNode) {
let ModifyNodes = [...nodes];
let findNode = nodes.findIndex(e => e.id === findBrothersNode?.id);
if (ModifyNodes[findNode]) {
ModifyNodes[findNode].text = ModifyNodes[findNode].text.concat(brothersNode);
const { width, height } = calculateSize(ModifyNodes[findNode].text, false);
ModifyNodes[findNode].width = width;
ModifyNodes[findNode].height = height;
nodes = [...ModifyNodes];
brothersNode = [];
}
} else {
const { width, height } = calculateSize(brothersNode, false);
const brothersNodeId = addNodes(brothersNode, width, height, false);
brothersNode = [];
if (brothersParentId) {
addEdges(brothersParentId, brothersNodeId);
} else {
notHaveParent = [...notHaveParent, brothersNodeId];
}
brothersNodeProps = [
...brothersNodeProps,
{
id: brothersNodeId,
parentId: brothersParentId,
objectsFromArrayId: objectsFromArray[objectsFromArray.length - 1],
},
];
}
}
// add parent node
const { width, height } = calculateSize(parentName, true);
parentId = addNodes(parentName, width, height, type);
bracketOpen = [...bracketOpen, { id: parentId, type: type }];
parentName = "";
// add edges from parent node
let brothersProps = brothersNodeProps.filter(
e =>
e.parentId === myParentId &&
e.objectsFromArrayId === objectsFromArray[objectsFromArray.length - 1]
);
if (
(brothersProps.length > 0 &&
bracketOpen[bracketOpen.length - 2] &&
bracketOpen[bracketOpen.length - 2].type !== "object") ||
(brothersProps.length > 0 && bracketOpen.length === 1)
) {
addEdges(brothersProps[brothersProps.length - 1].id, parentId);
} else if (myParentId) {
addEdges(myParentId, parentId);
} else {
notHaveParent = [...notHaveParent, parentId];
}
} else if (parentType === "array") {
objectsFromArray = [...objectsFromArray, objectsFromArrayId++];
}
children.forEach((branch, index, array) => {
if (array[index + 1]) {
traverse(
branch,
type,
bracketOpen[bracketOpen.length - 1]
? bracketOpen[bracketOpen.length - 1].id
: undefined,
array[index + 1].type
);
} else {
traverse(
branch,
type,
bracketOpen[bracketOpen.length - 1]
? bracketOpen[bracketOpen.length - 1].id
: undefined
);
}
});
if (type !== "property") {
// when children end
// add or concat brothers node when it is the last parent node
if (brothersNode.length > 0) {
let findBrothersNode = brothersNodeProps.find(
e =>
e.parentId === brothersParentId &&
e.objectsFromArrayId === objectsFromArray[objectsFromArray.length - 1]
);
if (findBrothersNode) {
let ModifyNodes = [...nodes];
let findNode = nodes.findIndex(e => e.id === findBrothersNode?.id);
if (ModifyNodes[findNode]) {
ModifyNodes[findNode].text = ModifyNodes[findNode].text.concat(brothersNode);
const { width, height } = calculateSize(ModifyNodes[findNode].text, false);
ModifyNodes[findNode].width = width;
ModifyNodes[findNode].height = height;
nodes = [...ModifyNodes];
brothersNode = [];
}
} else {
const { width, height } = calculateSize(brothersNode, false);
const brothersNodeId = addNodes(brothersNode, width, height, false);
brothersNode = [];
if (brothersParentId) {
addEdges(brothersParentId, brothersNodeId);
} else {
notHaveParent = [...notHaveParent, brothersNodeId];
}
brothersNodeProps = [
...brothersNodeProps,
{
id: brothersNodeId,
parentId: brothersParentId,
objectsFromArrayId: objectsFromArray[objectsFromArray.length - 1],
},
];
}
}
// close brackets
if (parentType !== "array") {
if (bracketOpen.length > 0) {
let newBracketOpen = [...bracketOpen];
newBracketOpen.splice(newBracketOpen.length - 1);
bracketOpen = [...newBracketOpen];
}
} else if (parentType === "array") {
if (objectsFromArray.length > 0) {
let newobjectsFromArray = [...objectsFromArray];
newobjectsFromArray.splice(newobjectsFromArray.length - 1);
objectsFromArray = [...newobjectsFromArray];
}
}
if (parentId) {
let myChildrens = edges.filter(e => e.from === parentId);
let myIndex = nodes.findIndex(e => e.id === parentId);
let ModifyNodes = [...nodes];
if (ModifyNodes[myIndex]) {
ModifyNodes[myIndex].data.childrenCount = myChildrens.length;
nodes = [...ModifyNodes];
}
}
}
}
});
};
if (json) {
traverse(json);
if (notHaveParent.length > 1) {
if (json.type !== "array") {
const text = "";
const { width, height } = calculateSize(text, false);
const emptyId = addNodes(text, width, height, false, true);
notHaveParent.forEach(children => {
addEdges(emptyId, children);
});
}
}
if (nodes.length === 0) {
if (json.type === "array") {
const text = "[]";
const { width, height } = calculateSize(text, false);
addNodes(text, width, height, false);
} else {
const text = "{}";
const { width, height } = calculateSize(text, false);
addNodes(text, width, height, false);
}
}
}
return { nodes, edges };
} catch (error) {
@@ -149,8 +334,3 @@ export const parser = (jsonStr: string, isExpanded = true) => {
};
}
};
function isNode(element: NodeData | EdgeData) {
if ("text" in element) return true;
return false;
}
+1 -4
View File
@@ -11,10 +11,7 @@ export const cleanupHighlight = () => {
});
};
export const highlightMatchedNodes = (
nodes: NodeListOf<Element>,
selectedNode: number
) => {
export const highlightMatchedNodes = (nodes: NodeListOf<Element>, selectedNode: number) => {
nodes?.forEach(node => {
node.parentElement?.closest("foreignObject")?.classList.add("searched");
});
+415 -114
View File
@@ -921,6 +921,13 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.3.1":
version "7.20.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd"
integrity sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==
dependencies:
regenerator-runtime "^0.13.11"
"@babel/template@^7.16.7", "@babel/template@^7.18.10":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71"
@@ -1291,6 +1298,11 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@react-oauth/google@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@react-oauth/google/-/google-0.4.0.tgz#ae4fe2724040bd11facdc53aad43a21e9f34b2c9"
integrity sha512-2QxxrKbXXH8bwHSefB56sBgsKs7Bq3Pvv8tVmGJuINGefECsssIUKidTDm5P55T4CV99sCX/GUfxs3l2Ntxo8Q==
"@rollup/plugin-babel@^5.2.0":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283"
@@ -1349,14 +1361,14 @@
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz#8be36a1f66f3265389e90b5f9c9962146758f728"
integrity sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==
"@sentry/browser@7.13.0":
version "7.13.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.13.0.tgz#883b8598c8a0c33af246242e7172e39306dc564a"
integrity sha512-WbgClHPYe8TKsdVVbuzd6alxwh3maFQNuljMkSTnYvPx2P+NT0wHljTs37D39FGfSmAwaqn7D/1ZHAtC+6mWxA==
"@sentry/browser@7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.16.0.tgz#afd2bac91857d2359272a0d9d2b1ee5ca7d69828"
integrity sha512-tJ063zvoF8Raw7mzQEXupOFPSN6v36WIbsDVGeFdToPCwViaBuATaxvWCrudGzsnBkMyItmTLJkzn9SEIXUOiw==
dependencies:
"@sentry/core" "7.13.0"
"@sentry/types" "7.13.0"
"@sentry/utils" "7.13.0"
"@sentry/core" "7.16.0"
"@sentry/types" "7.16.0"
"@sentry/utils" "7.16.0"
tslib "^1.9.3"
"@sentry/cli@^1.74.4":
@@ -1372,100 +1384,88 @@
proxy-from-env "^1.1.0"
which "^2.0.2"
"@sentry/core@7.13.0":
version "7.13.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.13.0.tgz#65597d71f8bfa1186f34009803e03ca9edb3adee"
integrity sha512-hB46fklmKrSDMEvZOF8qBHhys7PONBFyxQtbNDZUlv/kabs4gF3VEg1ftCaXnjx4lLNlsUl/ScFdM6194RvISg==
"@sentry/core@7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.16.0.tgz#60f9b54ef2ec524176b329e1d15be39c36da5953"
integrity sha512-vq6H1b/IPTvzDD9coQ3wIudvSjkAYuUlXb1dv69dRlq4v3st9dcKBps1Zf0lQ1i4TVlDLoe1iGMmNFglMF1Q5w==
dependencies:
"@sentry/hub" "7.13.0"
"@sentry/types" "7.13.0"
"@sentry/utils" "7.13.0"
"@sentry/types" "7.16.0"
"@sentry/utils" "7.16.0"
tslib "^1.9.3"
"@sentry/hub@7.13.0":
version "7.13.0"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-7.13.0.tgz#752068e528cfb277ed154bc94e311cad50ef792e"
integrity sha512-88/GsD1BoyrBwRKJCmVHZtSH5rizOsImUHWEXc1AOa1aR8nanfn56JdAbd6tC55pA+nT4R4H4vN/PrUaomTbtg==
"@sentry/integrations@7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.16.0.tgz#fa6a2a2b7275efec5cf67c07eb76297447301ff2"
integrity sha512-PJRmFUHOKMf1APOlwxGB7Z6LHzJP4xL6OLSoiwnruRuHaGvdyILA53fr/A6wRMSPyTxJDJpNkcN36cA56mLxYA==
dependencies:
"@sentry/types" "7.13.0"
"@sentry/utils" "7.13.0"
tslib "^1.9.3"
"@sentry/integrations@7.13.0":
version "7.13.0"
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.13.0.tgz#abd36fbf4a338877ca15ef5ed830163eeb6394ac"
integrity sha512-el5tonn/96fC+GMco70TXI2yrGmDLSFc0THKO/r9YEIJjqcK1KV1C6jJhTWt09ZBAgoeRCXSMn5xvdl3fc9Zrw==
dependencies:
"@sentry/types" "7.13.0"
"@sentry/utils" "7.13.0"
"@sentry/types" "7.16.0"
"@sentry/utils" "7.16.0"
localforage "^1.8.1"
tslib "^1.9.3"
"@sentry/nextjs@^7.13.0":
version "7.13.0"
resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-7.13.0.tgz#ff95e773e4e26beba030035937d2c8b1e5983d02"
integrity sha512-hKte/XGaWQUnVhndoEAA2MM7zGD7O7ZuEYj0wSCio6RfYK9QIPmi7lrLOy0YSDrY8EolvxDc1FxLOR/ygwkwHQ==
"@sentry/nextjs@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-7.16.0.tgz#07ca3d737d5eeacfe90f752b88e78d94b8a10b33"
integrity sha512-EqEfcL36TocxQZ820O2q01pF76oEEU4x8MzhRu4HrKByU7seUwU9m6a6YL8DmzcyJdenwL1HJK0Oyhg99pNcuw==
dependencies:
"@rollup/plugin-sucrase" "4.0.4"
"@sentry/core" "7.13.0"
"@sentry/hub" "7.13.0"
"@sentry/integrations" "7.13.0"
"@sentry/node" "7.13.0"
"@sentry/react" "7.13.0"
"@sentry/tracing" "7.13.0"
"@sentry/types" "7.13.0"
"@sentry/utils" "7.13.0"
"@sentry/core" "7.16.0"
"@sentry/integrations" "7.16.0"
"@sentry/node" "7.16.0"
"@sentry/react" "7.16.0"
"@sentry/tracing" "7.16.0"
"@sentry/types" "7.16.0"
"@sentry/utils" "7.16.0"
"@sentry/webpack-plugin" "1.19.0"
chalk "3.0.0"
rollup "2.78.0"
tslib "^1.9.3"
"@sentry/node@7.13.0":
version "7.13.0"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.13.0.tgz#0e073b75d4cce684db006d93ef18f2de2cf2f60e"
integrity sha512-uP3bPAIRHPilnOEiYGQQDLaQphc/c7d87wm91bZrTJ+WPnMW4D/NmT7fna5zGGDQIr/KTdQ/LEpDeZOILbkCqQ==
"@sentry/node@7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.16.0.tgz#f049e243169ec4d6058d5c0fff08104509f5ba9a"
integrity sha512-OC0TO6UTetv8IsX3zNhdeui7YVIQCnhkbfi+CMrB6YsHaMP2A9qH5gNyu/hKbaY9+4xci7e4rxyRmI65aKS9ow==
dependencies:
"@sentry/core" "7.13.0"
"@sentry/hub" "7.13.0"
"@sentry/types" "7.13.0"
"@sentry/utils" "7.13.0"
"@sentry/core" "7.16.0"
"@sentry/types" "7.16.0"
"@sentry/utils" "7.16.0"
cookie "^0.4.1"
https-proxy-agent "^5.0.0"
lru_map "^0.3.3"
tslib "^1.9.3"
"@sentry/react@7.13.0":
version "7.13.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.13.0.tgz#64fa5a2b944c977f75626c6208afa3478c13714c"
integrity sha512-ulvBmMiwt+4RXwnDkP9qNr7rMJIFE4QXuNxho5pIqWEU9q2656CoL5kau9f2TQQEBxNc9dR4QmUdGyzuEaYPIQ==
"@sentry/react@7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.16.0.tgz#7b214736804b7516ba66230301bc6f0202060a91"
integrity sha512-tJH5zO4MPopzqZgi7Yd4iIB9Am5uGVefgkHaoeCGXffveEVWqEdjj8TqPL36SBb76gCmtpa01NBDsUSDZ1w/yw==
dependencies:
"@sentry/browser" "7.13.0"
"@sentry/types" "7.13.0"
"@sentry/utils" "7.13.0"
"@sentry/browser" "7.16.0"
"@sentry/types" "7.16.0"
"@sentry/utils" "7.16.0"
hoist-non-react-statics "^3.3.2"
tslib "^1.9.3"
"@sentry/tracing@7.13.0":
version "7.13.0"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.13.0.tgz#521dc021dab78e37e29b0f90b01cb444337adfc4"
integrity sha512-/MKSd25rGv6Pc0FPBLXJifkfvSaYVPA8XUOLzVeDN0gl07h8AXli4qG9amTh/4Wb5h4dFpbcscOvW2VC+pxkIA==
"@sentry/tracing@7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.16.0.tgz#823b84abf0df08ecd16669547b4281e3c7a96ac9"
integrity sha512-8a9bViUY7oS35msBMouxkgz5OGd9NGutuKF7SQ8hPZwTcQPOxOwPyHccykgr56k6OmaDjGCLZMqZowaysJj+4g==
dependencies:
"@sentry/hub" "7.13.0"
"@sentry/types" "7.13.0"
"@sentry/utils" "7.13.0"
"@sentry/core" "7.16.0"
"@sentry/types" "7.16.0"
"@sentry/utils" "7.16.0"
tslib "^1.9.3"
"@sentry/types@7.13.0":
version "7.13.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.13.0.tgz#398e33e5c92ea0ce91e2c86e3ab003fe00c471a2"
integrity sha512-ttckM1XaeyHRLMdr79wmGA5PFbTGx2jio9DCD/mkEpSfk6OGfqfC7gpwy7BNstDH/VKyQj/lDCJPnwvWqARMoQ==
"@sentry/types@7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.16.0.tgz#79c06ada153a84feb949fa49b1c9d15f91decd79"
integrity sha512-i6D+OK6d0l/k+VQvRp/Pt21WkDEgVBUIZq+sOkEZJczbcfexVdXKeXXoYTD2vYuFq8Yy28fzlsZaKI+NoH94yQ==
"@sentry/utils@7.13.0":
version "7.13.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.13.0.tgz#0d47a9278806ece78ba3a83c7dbebce817462759"
integrity sha512-jnR85LgRLSk7IQe2OhKOPMY4fasJCNQNW0iCXsH+S2R1qnsF+N4ksNkQ+7JyyM9E7F03YpI2qd76bKY0VIn5iA==
"@sentry/utils@7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.16.0.tgz#b832520c661d4435808969ee04814ff8e20497b1"
integrity sha512-3Zh1txg7IRp4kZAdG27YF7K6lD1IZyuAo9KjoPg1Xzqa4DOZyASJuEkbf+rK2a9T4HrtVHHXJUsNbKg8WM3VHg==
dependencies:
"@sentry/types" "7.13.0"
"@sentry/types" "7.16.0"
tslib "^1.9.3"
"@sentry/webpack-plugin@1.19.0":
@@ -1475,6 +1475,11 @@
dependencies:
"@sentry/cli" "^1.74.4"
"@socket.io/component-emitter@~3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==
"@surma/rollup-plugin-off-main-thread@^2.2.3":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz#ee34985952ca21558ab0d952f00298ad2190c053"
@@ -1492,6 +1497,19 @@
dependencies:
tslib "^2.4.0"
"@tanstack/query-core@4.19.1":
version "4.19.1"
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.19.1.tgz#2e92d9e8a50884eb231c5beb4386e131ebe34306"
integrity sha512-Zp0aIose5C8skBzqbVFGk9HJsPtUhRVDVNWIqVzFbGQQgYSeLZMd3Sdb4+EnA5wl1J7X+bre2PJGnQg9x/zHOA==
"@tanstack/react-query@^4.19.1":
version "4.19.1"
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.19.1.tgz#43356dd537127e76d75f5a2769eb23dafd9a3690"
integrity sha512-5dvHvmc0vrWI03AJugzvKfirxCyCLe+qawrWFCXdu8t7dklIhJ7D5ZhgTypv7mMtIpdHPcECtCiT/+V74wCn2A==
dependencies:
"@tanstack/query-core" "4.19.1"
use-sync-external-store "^1.2.0"
"@testing-library/dom@^8.5.0":
version "8.18.1"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.18.1.tgz#80f91be02bc171fe5a3a7003f88207be31ac2cf3"
@@ -1566,6 +1584,13 @@
"@types/minimatch" "*"
"@types/node" "*"
"@types/hast@^2.0.0":
version "2.3.4"
resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.4.tgz#8aa5ef92c117d20d974a82bdfb6a648b08c0bafc"
integrity sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==
dependencies:
"@types/unist" "*"
"@types/hoist-non-react-statics@*":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
@@ -1584,6 +1609,23 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/lodash.debounce@^4.0.7":
version "4.0.7"
resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.7.tgz#0285879defb7cdb156ae633cecd62d5680eded9f"
integrity sha512-X1T4wMZ+gT000M2/91SYj0d/7JfeNZ9PeeOldSNoE/lunLeQXKvkmIumI29IaKMotU/ln/McOIvgzZcQ/3TrSA==
dependencies:
"@types/lodash" "*"
"@types/lodash@*":
version "4.14.191"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.191.tgz#09511e7f7cba275acd8b419ddac8da9a6a79e2fa"
integrity sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==
"@types/lz-string@^1.3.34":
version "1.3.34"
resolved "https://registry.yarnpkg.com/@types/lz-string/-/lz-string-1.3.34.tgz#69bfadde419314b4a374bf2c8e58659c035ed0a5"
integrity sha512-j6G1e8DULJx3ONf6NdR5JiR2ZY3K3PaaqiEuKYkLQO0Czfi1AzrtjfnfCROyWGeDd5IVMKCwsgSmMip9OWijow==
"@types/minimatch@*":
version "5.1.2"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca"
@@ -1614,6 +1656,13 @@
dependencies:
"@types/react" "*"
"@types/react-syntax-highlighter@^15.5.5":
version "15.5.5"
resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.5.tgz#4d3b51f8956195f1f63360ff03f8822c5d74c516"
integrity sha512-QH3JZQXa2usAvJvSsdSUJ4Yu4j8ReuZpgRrEW+XP+Rmosbn425YshW9iGEb/pAARm8496axHhHUPRH3UmTiB6A==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@18.0.21":
version "18.0.21"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.21.tgz#b8209e9626bb00a34c76f55482697edd2b43cc67"
@@ -1656,6 +1705,11 @@
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==
"@types/unist@*":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
"@typescript-eslint/parser@^5.21.0":
version "5.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.38.0.tgz#5a59a1ff41a7b43aacd1bb2db54f6bf1c02b2ff8"
@@ -1771,6 +1825,14 @@ allotment@^1.17.0:
lodash.isequal "^4.5.0"
use-resize-observer "^9.0.0"
altogic@^2.3.8:
version "2.3.8"
resolved "https://registry.yarnpkg.com/altogic/-/altogic-2.3.8.tgz#475ca083faec97660d30b838bbddaeeb83da9072"
integrity sha512-KTRSPH/g490sJiIe0qfuJaMsidGFkYSAnPR93Hn9VQ9GyZ+0/KmMIPSV6ctRaCvk/fw06w56IgYQNZDf6VJyxg==
dependencies:
cross-fetch "^3.1.4"
socket.io-client "^4.5.1"
ansi-regex@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
@@ -1899,6 +1961,11 @@ async@^3.2.3:
resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c"
integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
at-least-node@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
@@ -1909,6 +1976,15 @@ axe-core@^4.4.3:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.3.tgz#11c74d23d5013c0fa5d183796729bc3482bd2f6f"
integrity sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==
axios@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.1.3.tgz#8274250dada2edf53814ed7db644b9c2866c1e35"
integrity sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==
dependencies:
follow-redirects "^1.15.0"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
axobject-query@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
@@ -2076,6 +2152,21 @@ chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
character-entities-legacy@^1.0.0:
version "1.1.4"
resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1"
integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==
character-entities@^1.0.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b"
integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==
character-reference-invalid@^1.0.0:
version "1.1.4"
resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560"
integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==
classnames@^2.3.0, classnames@^2.3.1:
version "2.3.2"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
@@ -2117,6 +2208,18 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
comma-separated-tokens@^1.0.0:
version "1.0.8"
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea"
integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==
commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
@@ -2137,11 +2240,6 @@ commondir@^1.0.1:
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==
compress-json@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/compress-json/-/compress-json-2.1.2.tgz#37e0e7c7480c572fad9ad387fca5a2f36fee6f83"
integrity sha512-91247RD8bKQXzRmXUS4zGT250mhw86+J9X8w2L2SGtRE7g0CvzjOETFaFmsDdaXPWv8T7L9iiM7kdcnnH3BH7w==
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -2186,6 +2284,13 @@ create-require@^1.1.0:
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
cross-fetch@^3.1.4:
version "3.1.5"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
dependencies:
node-fetch "2.6.7"
cross-spawn@^7.0.2:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@@ -2236,7 +2341,12 @@ damerau-levenshtein@^1.0.8:
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
dayjs@^1.11.6:
version "1.11.6"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.6.tgz#2e79a226314ec3ec904e3ee1dd5a4f5e5b1c7afb"
integrity sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ==
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -2317,6 +2427,11 @@ del@^4.1.1:
pify "^4.0.1"
rimraf "^2.6.3"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
@@ -2399,7 +2514,23 @@ emojis-list@^3.0.0:
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
enhanced-resolve@^5.7.0:
engine.io-client@~6.2.3:
version "6.2.3"
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.2.3.tgz#a8cbdab003162529db85e9de31575097f6d29458"
integrity sha512-aXPtgF1JS3RuuKcpSrBtimSjYvrbhKW9froICH4s0F3XQWLxsKNxqzG39nnvQZQnva4CMvUK63T7shevxRyYHw==
dependencies:
"@socket.io/component-emitter" "~3.1.0"
debug "~4.3.1"
engine.io-parser "~5.0.3"
ws "~8.2.3"
xmlhttprequest-ssl "~2.0.0"
engine.io-parser@~5.0.3:
version "5.0.4"
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.4.tgz#0b13f704fa9271b3ec4f33112410d8f3f41d0fc0"
integrity sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==
enhanced-resolve@^5.10.0:
version "5.10.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz#0dc579c3bb2a1032e357ac45b8f3a6f3ad4fb1e6"
integrity sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==
@@ -2765,6 +2896,13 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
fault@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13"
integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==
dependencies:
format "^0.2.0"
file-entry-cache@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
@@ -2824,6 +2962,11 @@ flatted@^3.1.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
follow-redirects@^1.15.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
for-each@~0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
@@ -2831,6 +2974,20 @@ for-each@~0.3.3:
dependencies:
is-callable "^1.1.3"
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
format@^0.2.0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==
framer-motion@^6.2.8:
version "6.5.1"
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-6.5.1.tgz#802448a16a6eb764124bf36d8cbdfa6dd6b931a7"
@@ -3079,11 +3236,32 @@ has@^1.0.3, has@~1.0.3:
dependencies:
function-bind "^1.1.1"
hast-util-parse-selector@^2.0.0:
version "2.2.5"
resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a"
integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==
hastscript@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640"
integrity sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==
dependencies:
"@types/hast" "^2.0.0"
comma-separated-tokens "^1.0.0"
hast-util-parse-selector "^2.0.0"
property-information "^5.0.0"
space-separated-tokens "^1.0.0"
hey-listen@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
highlight.js@^10.4.1, highlight.js@~10.7.0:
version "10.7.3"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531"
integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==
hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
@@ -3154,6 +3332,19 @@ internal-slot@^1.0.3:
has "^1.0.3"
side-channel "^1.0.4"
is-alphabetical@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d"
integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==
is-alphanumerical@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf"
integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==
dependencies:
is-alphabetical "^1.0.0"
is-decimal "^1.0.0"
is-arguments@^1.0.4:
version "1.1.1"
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
@@ -3196,6 +3387,11 @@ is-date-object@^1.0.1:
dependencies:
has-tostringtag "^1.0.0"
is-decimal@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5"
integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@@ -3220,6 +3416,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
dependencies:
is-extglob "^2.1.1"
is-hexadecimal@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7"
integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==
is-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
@@ -3410,9 +3611,9 @@ json5@^1.0.1:
minimist "^1.2.0"
json5@^2.1.2, json5@^2.2.0, json5@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
version "2.2.2"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.2.tgz#64471c5bdcc564c18f7c1d4df2e2297f2457c5ab"
integrity sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==
jsonc-parser@^3.2.0:
version "3.2.0"
@@ -3503,9 +3704,9 @@ lines-and-columns@^1.1.6:
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
loader-utils@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129"
integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==
version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
@@ -3574,6 +3775,14 @@ loose-envify@^1.1.0, loose-envify@^1.4.0:
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
lowlight@^1.17.0:
version "1.20.0"
resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.20.0.tgz#ddb197d33462ad0d93bf19d17b6c301aa3941888"
integrity sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==
dependencies:
fault "^1.0.0"
highlight.js "~10.7.0"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
@@ -3633,6 +3842,18 @@ micromatch@^4.0.4:
braces "^3.0.2"
picomatch "^2.3.1"
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.1.12:
version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
@@ -3710,12 +3931,12 @@ next-pwa@5.6.0:
workbox-webpack-plugin "^6.5.4"
workbox-window "^6.5.4"
next-transpile-modules@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/next-transpile-modules/-/next-transpile-modules-9.0.0.tgz#133b1742af082e61cc76b02a0f12ffd40ce2bf90"
integrity sha512-VCNFOazIAnXn1hvgYYSTYMnoWgKgwlYh4lm1pKbSfiB3kj5ZYLcKVhfh3jkPOg1cnd9DP+pte9yCUocdPEUBTQ==
next-transpile-modules@^9.1.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/next-transpile-modules/-/next-transpile-modules-9.1.0.tgz#dffd2563bf76f8afdb28f0611948f46252ca65ef"
integrity sha512-yzJji65xDqcIqjvx5vPJcs1M+MYQTzLM1pXH/qf8Q88ohx+bwVGDc1AeV+HKr1NwvMCNTpwVPSFI7cA5WdyeWA==
dependencies:
enhanced-resolve "^5.7.0"
enhanced-resolve "^5.10.0"
escalade "^3.1.1"
next@^12.3.1:
@@ -3744,7 +3965,7 @@ next@^12.3.1:
"@next/swc-win32-ia32-msvc" "12.3.1"
"@next/swc-win32-x64-msvc" "12.3.1"
node-fetch@^2.6.7:
node-fetch@2.6.7, node-fetch@^2.6.7:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
@@ -3908,6 +4129,18 @@ parent-module@^1.0.0:
dependencies:
callsites "^3.0.0"
parse-entities@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8"
integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==
dependencies:
character-entities "^1.0.0"
character-entities-legacy "^1.0.0"
character-reference-invalid "^1.0.0"
is-alphanumerical "^1.0.0"
is-decimal "^1.0.0"
is-hexadecimal "^1.0.0"
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
@@ -4035,6 +4268,16 @@ pretty-format@^27.0.2:
ansi-styles "^5.0.0"
react-is "^17.0.1"
prismjs@^1.27.0:
version "1.29.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12"
integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==
prismjs@~1.27.0:
version "1.27.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057"
integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==
process-nextick-args@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
@@ -4054,6 +4297,13 @@ prop-types@^15.5.10, prop-types@^15.7.2, prop-types@^15.8.1:
object-assign "^4.1.1"
react-is "^16.13.1"
property-information@^5.0.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69"
integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==
dependencies:
xtend "^4.0.0"
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
@@ -4123,10 +4373,10 @@ react-hot-toast@^2.4.0:
dependencies:
goober "^2.1.10"
react-icons@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.4.0.tgz#a13a8a20c254854e1ec9aecef28a95cdf24ef703"
integrity sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg==
react-icons@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.6.0.tgz#f83eda179af5d02c047449a20b702c858653d397"
integrity sha512-rR/L9m9340yO8yv1QT1QurxWQvWpbNHqVX0fzMln2HEb9TEIrQRGsqiNFQfiv9/JEUbyHmHPlNTB2LWm2Ttz0g==
react-in-viewport@^1.0.0-alpha.28:
version "1.0.0-alpha.28"
@@ -4157,12 +4407,16 @@ react-scrolllock@^5.0.1:
dependencies:
exenv "^1.2.2"
react-twitter-embed@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/react-twitter-embed/-/react-twitter-embed-4.0.4.tgz#4a6b8354acc266876ff1110b9f648518ea20db6d"
integrity sha512-2JIL7qF+U62zRzpsh6SZDXNI3hRNVYf5vOZ1WRcMvwKouw+xC00PuFaD0aEp2wlyGaZ+f4x2VvX+uDadFQ3HVA==
react-syntax-highlighter@^15.5.0:
version "15.5.0"
resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz#4b3eccc2325fa2ec8eff1e2d6c18fa4a9e07ab20"
integrity sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==
dependencies:
scriptjs "^2.5.9"
"@babel/runtime" "^7.3.1"
highlight.js "^10.4.1"
lowlight "^1.17.0"
prismjs "^1.27.0"
refractor "^3.6.0"
react-use-gesture@^8.0.1:
version "8.0.1"
@@ -4229,6 +4483,15 @@ reakeys@^1.2.6:
dependencies:
mousetrap "^1.6.5"
refractor@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.6.0.tgz#ac318f5a0715ead790fcfb0c71f4dd83d977935a"
integrity sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==
dependencies:
hastscript "^6.0.0"
parse-entities "^2.0.0"
prismjs "~1.27.0"
regenerate-unicode-properties@^10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c"
@@ -4241,6 +4504,11 @@ regenerate@^1.4.2:
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
regenerator-runtime@^0.13.11:
version "0.13.11"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
regenerator-runtime@^0.13.4:
version "0.13.9"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
@@ -4420,11 +4688,6 @@ schema-utils@^3.1.1:
ajv "^6.12.5"
ajv-keywords "^3.5.2"
scriptjs@^2.5.9:
version "2.5.9"
resolved "https://registry.yarnpkg.com/scriptjs/-/scriptjs-2.5.9.tgz#343915cd2ec2ed9bfdde2b9875cd28f59394b35f"
integrity sha512-qGVDoreyYiP1pkQnbnFAUIS5AjenNwwQBdl7zeos9etl+hYKWahjRTfzAZZYBv5xNHx7vNKCmaLDQZ6Fr2AEXg==
semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
@@ -4492,6 +4755,24 @@ slash@^3.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
socket.io-client@^4.5.1:
version "4.5.4"
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.5.4.tgz#d3cde8a06a6250041ba7390f08d2468ccebc5ac9"
integrity sha512-ZpKteoA06RzkD32IbqILZ+Cnst4xewU7ZYK12aS1mzHftFFjpoMz69IuhP/nL25pJfao/amoPI527KnuhFm01g==
dependencies:
"@socket.io/component-emitter" "~3.1.0"
debug "~4.3.2"
engine.io-client "~6.2.3"
socket.io-parser "~4.2.1"
socket.io-parser@~4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.1.tgz#01c96efa11ded938dcb21cbe590c26af5eff65e5"
integrity sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==
dependencies:
"@socket.io/component-emitter" "~3.1.0"
debug "~4.3.1"
source-list-map@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
@@ -4532,6 +4813,11 @@ sourcemap-codec@^1.4.8:
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
space-separated-tokens@^1.0.0:
version "1.1.5"
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899"
integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==
state-local@^1.0.6:
version "1.0.7"
resolved "https://registry.yarnpkg.com/state-local/-/state-local-1.0.7.tgz#da50211d07f05748d53009bee46307a37db386d5"
@@ -4649,10 +4935,10 @@ style-value-types@5.0.0:
hey-listen "^1.0.8"
tslib "^2.1.0"
styled-components@^5.3.5:
version "5.3.5"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.5.tgz#a750a398d01f1ca73af16a241dec3da6deae5ec4"
integrity sha512-ndETJ9RKaaL6q41B69WudeqLzOpY1A/ET/glXkNZ2T7dPjPqpPCXXQjDFYZWwNnE5co0wX+gTCqx9mfxTmSIPg==
styled-components@^5.3.6:
version "5.3.6"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.6.tgz#27753c8c27c650bee9358e343fc927966bfd00d1"
integrity sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/traverse" "^7.4.5"
@@ -4976,7 +5262,7 @@ use-resize-observer@^9.0.0:
dependencies:
"@juggle/resize-observer" "^3.3.1"
use-sync-external-store@1.2.0:
use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
@@ -5230,6 +5516,21 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
ws@~8.2.3:
version "8.2.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba"
integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==
xmlhttprequest-ssl@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67"
integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==
xtend@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
@@ -5245,9 +5546,9 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zustand@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.1.1.tgz#5a61cc755a002df5f041840a414ae6e9a99ee22b"
integrity sha512-h4F3WMqsZgvvaE0n3lThx4MM81Ls9xebjvrABNzf5+jb3/03YjNTSgZXeyrvXDArMeV9untvWXRw1tY+ntPYbA==
zustand@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.1.3.tgz#72bc4c0ed8ed906fbd92c7c20cde8dd6114c018f"
integrity sha512-AdFyr6+4sVD6xlyc/ArQaOrleqzxJEBbAXglufZ5lgvisoz8GUN3icOrKOnX1uRSxmpmdVUQPen9hhymWIzhBg==
dependencies:
use-sync-external-store "1.2.0"