Skip to content

Commit

Permalink
Merge pull request #367 from AykutSarac/feat/jsongraph-react
Browse files Browse the repository at this point in the history
feat: UI updates
  • Loading branch information
AykutSarac authored Dec 17, 2023
2 parents 1754cc5 + bdf0aa7 commit 26d06ff
Show file tree
Hide file tree
Showing 45 changed files with 2,690 additions and 3,235 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@
]
},
"extends": ["next/core-web-vitals", "prettier"],
"plugins": ["prettier", "unused-imports"]
"plugins": ["prettier", "unused-imports"],
"ignorePatterns": ["src/enums"]
}
10 changes: 5 additions & 5 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
node-version: [18.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
Expand All @@ -18,7 +18,7 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm install
- run: npm run lint
- run: npm run build
cache: 'yarn'
- run: yarn install
- run: yarn lint
- run: yarn build
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ node_modules/
out
public
*-lock.json
tsconfig.json
tsconfig.json
6 changes: 3 additions & 3 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
JSON Crack
Copyright (C) 2023 Aykut Saraç

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand All @@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:

<program> Copyright (C) <year> <name of author>
JSON Crack Copyright (C) 2023 Aykut Saraç
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
Expand Down
5 changes: 5 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ const config = {
compiler: {
styledComponents: true,
},
webpack: config => {
config.resolve.fallback = { fs: false };

return config;
},
};

const bundleAnalyzerConfig = withBundleAnalyzer(config);
Expand Down
38 changes: 20 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
"name": "json-crack",
"private": true,
"version": "v3.0.0",
"license": "GPL-3.0",
"author": "https://github.com/AykutSarac",
"homepage": "https://jsoncrack.com",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "tsc && eslint src && prettier --check src",
"lint": "tsc --project tsconfig.json && eslint src && prettier --check src",
"lint:fix": "eslint --fix src & prettier --write src",
"analyze": "ANALYZE=true npm run build"
},
Expand All @@ -25,52 +26,53 @@
"@supabase/auth-helpers-react": "^0.4.2",
"@supabase/supabase-js": "^2.36.0",
"@tanstack/react-query": "^4.35.3",
"allotment": "^1.19.2",
"allotment": "^1.19.3",
"axios": "^1.5.0",
"dayjs": "^1.11.10",
"html-to-image": "^1.11.11",
"jq-in-the-browser": "^0.7.2",
"json-2-csv": "^4.1.0",
"jq-web": "^0.5.1",
"json-2-csv": "^5.0.1",
"json-to-ts": "^1.7.0",
"jsonc-parser": "^3.2.0",
"jsonwebtoken": "^9.0.2",
"jxon": "^2.0.0-beta.5",
"lodash.debounce": "^4.0.8",
"lodash.get": "^4.4.2",
"lodash.set": "^4.3.2",
"maketypes": "^1.1.2",
"next": "13.4.12",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-ga4": "^2.1.0",
"react-hot-toast": "^2.4.1",
"react-icons": "^4.11.0",
"react-icons": "^4.12.0",
"react-json-tree": "^0.18.0",
"react-linkify-it": "^1.0.7",
"react-simple-typewriter": "^5.0.1",
"react-zoomable-ui": "^0.11.0",
"reaflow": "5.2.6",
"styled-components": "^6.0.8",
"reaflow": "5.2.8",
"styled-components": "^6.1.1",
"toml": "^3.0.0",
"use-long-press": "^3.1.5",
"zustand": "^4.4.0"
"zustand": "^4.4.7"
},
"devDependencies": {
"@next/bundle-analyzer": "^13.4.12",
"@testing-library/react": "^14.0.0",
"@trivago/prettier-plugin-sort-imports": "^4.2.0",
"@types/jsonwebtoken": "^9.0.3",
"@types/jxon": "^2.0.2",
"@types/lodash.debounce": "^4.0.7",
"@types/lodash.get": "^4.4.7",
"@types/lodash.set": "^4.3.7",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/jsonwebtoken": "^9.0.5",
"@types/jxon": "^2.0.5",
"@types/lodash.debounce": "^4.0.9",
"@types/lodash.get": "^4.4.9",
"@types/lodash.set": "^4.3.9",
"@types/node": "^20.4.7",
"@types/react": "18.2.18",
"@types/react-color": "^3.0.6",
"@types/react-syntax-highlighter": "^15.5.7",
"eslint": "8.46.0",
"eslint": "8.56.0",
"eslint-config-next": "13.4.12",
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-unused-imports": "^3.0.0",
"prettier": "^3.0.1",
"prettier": "^3.1.1",
"ts-node": "^10.9.1",
"typescript": "5.1.6"
}
Expand Down
16 changes: 8 additions & 8 deletions src/components/Graph/CustomNode/TextNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { MdLink, MdLinkOff } from "react-icons/md";
import { CustomNodeProps } from "src/components/Graph/CustomNode";
import useToggleHide from "src/hooks/useToggleHide";
import { isContentImage } from "src/lib/utils/graph/calculateNodeSize";
import useConfig from "src/store/useConfig";
import useGraph from "src/store/useGraph";
import useStored from "src/store/useStored";
import { TextRenderer } from "./TextRenderer";
import * as Styled from "./styles";

Expand Down Expand Up @@ -52,13 +52,13 @@ const Node: React.FC<CustomNodeProps> = ({ node, x, y, hasCollapse = false }) =>
data: { isParent, childrenCount, type },
} = node;
const { validateHiddenNodes } = useToggleHide();
const hideCollapse = useStored(state => state.hideCollapse);
const showChildrenCount = useStored(state => state.childrenCount);
const imagePreview = useStored(state => state.imagePreview);
const collapseButtonVisible = useConfig(state => state.collapseButtonVisible);
const childrenCountVisible = useConfig(state => state.childrenCountVisible);
const imagePreviewEnabled = useConfig(state => state.imagePreviewEnabled);
const expandNodes = useGraph(state => state.expandNodes);
const collapseNodes = useGraph(state => state.collapseNodes);
const isExpanded = useGraph(state => state.collapsedParents.includes(id));
const isImage = imagePreview && isContentImage(text as string);
const isImage = imagePreviewEnabled && isContentImage(text as string);
const value = JSON.stringify(text).replaceAll('"', "");

const handleExpand = (e: React.MouseEvent<HTMLButtonElement>) => {
Expand All @@ -80,16 +80,16 @@ const Node: React.FC<CustomNodeProps> = ({ node, x, y, hasCollapse = false }) =>
data-x={x}
data-y={y}
data-key={JSON.stringify(text)}
$hasCollapse={isParent && hideCollapse}
$hasCollapse={isParent && collapseButtonVisible}
>
<Styled.StyledKey $value={value} $parent={isParent} $type={type}>
<TextRenderer>{value}</TextRenderer>
</Styled.StyledKey>
{isParent && childrenCount > 0 && showChildrenCount && (
{isParent && childrenCount > 0 && childrenCountVisible && (
<Styled.StyledChildrenCount>({childrenCount})</Styled.StyledChildrenCount>
)}

{isParent && hasCollapse && hideCollapse && (
{isParent && hasCollapse && collapseButtonVisible && (
<StyledExpand aria-label="Expand" onClick={handleExpand}>
{isExpanded ? <MdLinkOff size={18} /> : <MdLink size={18} />}
</StyledExpand>
Expand Down
8 changes: 4 additions & 4 deletions src/components/Graph/CustomNode/TextRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ const isURL =
/(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/gi;

export const TextRenderer: React.FC<{ children: string }> = ({ children }) => {
if (isURL.test(children.replaceAll('"', ""))) {
if (isURL.test(children?.replaceAll('"', ""))) {
return <Styled.StyledLinkItUrl>{children}</Styled.StyledLinkItUrl>;
}

if (isColorFormat(children.replaceAll('"', ""))) {
if (isColorFormat(children?.replaceAll('"', ""))) {
return (
<StyledRow>
<ColorSwatch radius={4} h={12} w={12} color={children.replaceAll('"', "")} />
{children.replaceAll('"', "")}
<ColorSwatch radius={4} h={12} w={12} mr={8} color={children?.replaceAll('"', "")} />
{children?.replaceAll('"', "")}
</StyledRow>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Graph/CustomNode/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import dynamic from "next/dynamic";
import { NodeProps } from "reaflow";
import useGraph from "src/store/useGraph";
import useModal from "src/store/useModal";
import { NodeData } from "src/types/models";
import { NodeData } from "src/types/graph";
import { ObjectNode } from "./ObjectNode";
import { TextNode } from "./TextNode";

Expand Down
58 changes: 38 additions & 20 deletions src/components/Graph/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import React from "react";
import dynamic from "next/dynamic";
import styled from "styled-components";
import { toast } from "react-hot-toast";
import { Space } from "react-zoomable-ui";
import { ElkRoot } from "reaflow/dist/layout/useLayout";
import { useLongPress } from "use-long-press";
import { CustomNode } from "src/components/Graph/CustomNode";
import { ViewMode } from "src/enums/viewMode.enum";
import useToggleHide from "src/hooks/useToggleHide";
import { Loading } from "src/layout/Loading";
import useConfig from "src/store/useConfig";
import useGraph from "src/store/useGraph";
import useStored from "src/store/useStored";
import useUser from "src/store/useUser";
import { NodeData } from "src/types/models";
import { NodeData } from "src/types/graph";
import { CustomEdge } from "./CustomEdge";
import { ErrorView } from "./ErrorView";
import { PremiumView } from "./PremiumView";
Expand All @@ -23,7 +25,7 @@ interface GraphProps {
isWidget?: boolean;
}

const StyledEditorWrapper = styled.div<{ $widget: boolean }>`
const StyledEditorWrapper = styled.div<{ $widget: boolean; $showRulers: boolean }>`
position: absolute;
width: 100%;
height: ${({ $widget }) => ($widget ? "calc(100vh - 36px)" : "calc(100vh - 63px)")};
Expand All @@ -33,20 +35,24 @@ const StyledEditorWrapper = styled.div<{ $widget: boolean }>`
--line-color-2: ${({ theme }) => theme.GRID_COLOR_SECONDARY};
background-color: var(--bg-color);
background-image: linear-gradient(var(--line-color-1) 1.5px, transparent 1.5px),
linear-gradient(90deg, var(--line-color-1) 1.5px, transparent 1.5px),
linear-gradient(var(--line-color-2) 1px, transparent 1px),
linear-gradient(90deg, var(--line-color-2) 1px, transparent 1px);
background-position:
-1.5px -1.5px,
-1.5px -1.5px,
-1px -1px,
-1px -1px;
background-size:
100px 100px,
100px 100px,
20px 20px,
20px 20px;
${({ $showRulers }) =>
$showRulers &&
`
background-image: linear-gradient(var(--line-color-1) 1.5px, transparent 1.5px),
linear-gradient(90deg, var(--line-color-1) 1.5px, transparent 1.5px),
linear-gradient(var(--line-color-2) 1px, transparent 1px),
linear-gradient(90deg, var(--line-color-2) 1px, transparent 1px);
background-position:
-1.5px -1.5px,
-1.5px -1.5px,
-1px -1px,
-1px -1px;
background-size:
100px 100px,
100px 100px,
20px 20px,
20px 20px;
`};
:active {
cursor: move;
Expand Down Expand Up @@ -76,7 +82,8 @@ const layoutOptions = {
};

const PREMIUM_LIMIT = 200;
const ERROR_LIMIT = 3_000;
const ERROR_LIMIT_TREE = 5_000;
const ERROR_LIMIT = 10_000;

const GraphCanvas = ({ isWidget }: GraphProps) => {
const { validateHiddenNodes } = useToggleHide();
Expand Down Expand Up @@ -138,6 +145,7 @@ const GraphCanvas = ({ isWidget }: GraphProps) => {

function getViewType(nodes: NodeData[]) {
if (nodes.length > ERROR_LIMIT) return "error";
if (nodes.length > ERROR_LIMIT_TREE) return "tree";
if (nodes.length > PREMIUM_LIMIT) return "premium";
return "graph";
}
Expand All @@ -147,7 +155,9 @@ export const Graph = ({ isWidget = false }: GraphProps) => {
const loading = useGraph(state => state.loading);
const isPremium = useUser(state => state.premium);
const viewType = useGraph(state => getViewType(state.nodes));
const gesturesEnabled = useStored(state => state.gesturesEnabled);
const gesturesEnabled = useConfig(state => state.gesturesEnabled);
const rulersEnabled = useConfig(state => state.rulersEnabled);
const setViewMode = useConfig(state => state.setViewMode);

const callback = React.useCallback(() => {
const canvas = document.querySelector(".jsoncrack-canvas") as HTMLDivElement | null;
Expand All @@ -166,7 +176,14 @@ export const Graph = ({ isWidget = false }: GraphProps) => {
if ("activeElement" in document) (document.activeElement as HTMLElement)?.blur();
}, []);

if (viewType === "error") return <ErrorView />;
if (viewType === "error") {
return <ErrorView />;
}

if (viewType === "tree") {
setViewMode(ViewMode.Tree);
toast("This document is too large to display as a graph. Switching to tree view.");
}

if (viewType === "premium" && !isWidget) {
if (!isPremium) return <PremiumView />;
Expand All @@ -180,6 +197,7 @@ export const Graph = ({ isWidget = false }: GraphProps) => {
onContextMenu={e => e.preventDefault()}
onClick={blurOnClick}
key={String(gesturesEnabled)}
$showRulers={rulersEnabled}
{...bindLongPress()}
>
<Space
Expand Down
4 changes: 2 additions & 2 deletions src/components/MonacoEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import React from "react";
import styled from "styled-components";
import Editor, { loader, useMonaco } from "@monaco-editor/react";
import { Loading } from "src/layout/Loading";
import useConfig from "src/store/useConfig";
import useFile from "src/store/useFile";
import useStored from "src/store/useStored";

loader.config({
paths: {
Expand Down Expand Up @@ -33,7 +33,7 @@ export const MonacoEditor = () => {
const setError = useFile(state => state.setError);
const jsonSchema = useFile(state => state.jsonSchema);
const getHasChanges = useFile(state => state.getHasChanges);
const theme = useStored(state => (state.lightmode ? "light" : "vs-dark"));
const theme = useConfig(state => (state.darkmodeEnabled ? "vs-dark" : "light"));
const fileType = useFile(state => state.format);

React.useEffect(() => {
Expand Down
Loading

0 comments on commit 26d06ff

Please sign in to comment.