refactor codebase

This commit is contained in:
AykutSarac 2024-09-11 19:59:11 +03:00
parent 6b0c6632a9
commit b797fedced
No known key found for this signature in database
69 changed files with 189 additions and 403 deletions

2
.gitignore vendored
View File

@ -40,5 +40,3 @@ npm-debug.log*
**/public/fallback-*.js
# Sentry Auth Token
.sentryclirc
/src/migration

View File

@ -2,8 +2,14 @@
"name": "json-crack",
"private": true,
"version": "0.0.0",
"author": "https://github.com/AykutSarac",
"author": {
"name": "JSON Crack",
"email": "contact@jsoncrack.com"
},
"homepage": "https://jsoncrack.com",
"bugs": {
"url": "https://github.com/AykutSarac/jsoncrack.com/issues"
},
"scripts": {
"dev": "next dev",
"build": "next build",

View File

@ -1,36 +0,0 @@
// Example taken from https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json
const sampleJson = Object.freeze({
squadName: "Super hero squad",
homeTown: "Metro City",
formed: 2016,
secretBase: "Super tower",
active: true,
members: [
{
name: "Molecule Man",
age: 29,
secretIdentity: "Dan Jukes",
powers: ["Radiation resistance", "Turning tiny", "Radiation blast"],
},
{
name: "Madame Uppercut",
age: 39,
secretIdentity: "Jane Wilson",
powers: ["Million tonne punch", "Damage resistance", "Superhuman reflexes"],
},
{
name: "Eternal Flame",
age: 1000000,
secretIdentity: "Unknown",
powers: [
"Immortality",
"Heat Immunity",
"Inferno",
"Teleportation",
"Interdimensional travel",
],
},
],
});
export const defaultJson = JSON.stringify(sampleJson, null, 2);

View File

@ -1,34 +0,0 @@
export const images = Object.freeze([
{
id: 1,
alt: "⚡️ Clean and Intuitive Interface designed for productivity and ease of use.",
},
{
id: 2,
alt: "🔎 Enhanced Search Experience, with the ability to focus on node as you navigate through the data.",
},
{
id: 3,
alt: "✍️ Understand your data in less time, modify the data directly on the graph.",
},
{
id: 4,
alt: "📋 Download graphs as images, with options to customize the image quality and background color.",
},
{
id: 5,
alt: "🎨 Customize the graph appearance, with options to change the node and edge colors, and the graph layout. Match the graph to your brand colors.",
},
{
id: 6,
alt: "🖍️ Compare data on the graph, with the ability to highlight the differences between two datasets.",
},
{
id: 7,
alt: "🤖 Filter and transform your data quickly and securely using the AI powered assistant.",
},
{
id: 8,
alt: "🌓 Who doesn't love dark mode? A sleek and modern design that is easy on the eyes.",
},
]);

View File

@ -1,8 +1,9 @@
import React from "react";
import Link from "next/link";
import { Flex, Popover, Text } from "@mantine/core";
import styled from "styled-components";
import { event as gaEvent } from "nextjs-google-analytics";
import { AiOutlineLink, AiOutlineLock, AiOutlineUnlock } from "react-icons/ai";
import { AiOutlineLock, AiOutlineUnlock } from "react-icons/ai";
import { BiSolidDockLeft } from "react-icons/bi";
import {
VscCheck,
@ -13,7 +14,7 @@ import {
VscSync,
VscSyncIgnored,
} from "react-icons/vsc";
import useGraph from "src/modules/GraphView/stores/useGraph";
import useGraph from "src/containers/Editor/components/views/GraphView/stores/useGraph";
import useConfig from "src/store/useConfig";
import useFile from "src/store/useFile";
import useModal from "src/store/useModal";
@ -114,7 +115,6 @@ export const BottomBar = () => {
<StyledBottomBarItem onClick={toggleEditor}>
<BiSolidDockLeft />
</StyledBottomBarItem>
{fileName && (
<StyledBottomBarItem onClick={() => setVisible("cloud")(true)}>
<VscSourceControl />
@ -149,12 +149,6 @@ export const BottomBar = () => {
{isPrivate ? "Private" : "Public"}
</StyledBottomBarItem>
)}
{isAuthenticated && (
<StyledBottomBarItem onClick={() => setVisible("notice")(true)}>
<AiOutlineLink />
Share
</StyledBottomBarItem>
)}
<StyledBottomBarItem
onClick={() => {
toggleLiveTransform(!liveTransformEnabled);
@ -174,10 +168,12 @@ export const BottomBar = () => {
<StyledRight>
<StyledBottomBarItem>Nodes: {nodeCount}</StyledBottomBarItem>
<StyledBottomBarItem onClick={() => setVisible("review")(true)}>
<VscFeedback />
Feedback
</StyledBottomBarItem>
<Link href="https://github.com/AykutSarac/jsoncrack.com/discussions" target="_blank">
<StyledBottomBarItem>
<VscFeedback />
Feedback
</StyledBottomBarItem>
</Link>
</StyledRight>
</StyledBottomBar>
);

View File

@ -1,8 +1,8 @@
import React from "react";
import styled from "styled-components";
import { GraphView } from "src/containers/Editor/components/views/GraphView";
import { TreeView } from "src/containers/Editor/components/views/TreeView";
import { ViewMode } from "src/enums/viewMode.enum";
import { GraphView } from "src/modules/GraphView";
import { TreeView } from "src/modules/TreeView";
import useConfig from "src/store/useConfig";
const StyledLiveEditor = styled.div`

View File

@ -1,5 +1,5 @@
import React from "react";
import type { CustomNodeProps } from "src/modules/GraphView/CustomNode";
import type { CustomNodeProps } from "src/containers/Editor/components/views/GraphView/CustomNode";
import { TextRenderer } from "./TextRenderer";
import * as Styled from "./styles";

View File

@ -1,10 +1,10 @@
import React from "react";
import styled from "styled-components";
import { MdLink, MdLinkOff } from "react-icons/md";
import type { CustomNodeProps } from "src/containers/Editor/components/views/GraphView/CustomNode";
import { isContentImage } from "src/containers/Editor/components/views/GraphView/lib/utils/calculateNodeSize";
import useGraph from "src/containers/Editor/components/views/GraphView/stores/useGraph";
import useToggleHide from "src/hooks/useToggleHide";
import type { CustomNodeProps } from "src/modules/GraphView/CustomNode";
import { isContentImage } from "src/modules/GraphView/lib/utils/calculateNodeSize";
import useGraph from "src/modules/GraphView/stores/useGraph";
import useConfig from "src/store/useConfig";
import { TextRenderer } from "./TextRenderer";
import * as Styled from "./styles";

View File

@ -1,7 +1,7 @@
import React from "react";
import type { NodeProps } from "reaflow";
import { Node } from "reaflow";
import useGraph from "src/modules/GraphView/stores/useGraph";
import useGraph from "src/containers/Editor/components/views/GraphView/stores/useGraph";
import useModal from "src/store/useModal";
import type { NodeData } from "src/types/graph";
import { ObjectNode } from "./ObjectNode";

View File

@ -6,9 +6,9 @@ import { Space } from "react-zoomable-ui";
import { Canvas } from "reaflow";
import type { ElkRoot } from "reaflow/dist/layout/useLayout";
import { useLongPress } from "use-long-press";
import { CustomNode } from "src/containers/Editor/components/views/GraphView/CustomNode";
import useGraph from "src/containers/Editor/components/views/GraphView/stores/useGraph";
import useToggleHide from "src/hooks/useToggleHide";
import { CustomNode } from "src/modules/GraphView/CustomNode";
import useGraph from "src/modules/GraphView/stores/useGraph";
import useConfig from "src/store/useConfig";
import { CustomEdge } from "./CustomEdge";
import { NotSupported } from "./NotSupported";

View File

@ -1,6 +1,6 @@
import type { NodeType } from "jsonc-parser";
import type { Graph } from "src/modules/GraphView/lib/jsonParser";
import { calculateNodeSize } from "src/modules/GraphView/lib/utils/calculateNodeSize";
import type { Graph } from "src/containers/Editor/components/views/GraphView/lib/jsonParser";
import { calculateNodeSize } from "src/containers/Editor/components/views/GraphView/lib/utils/calculateNodeSize";
type Props = {
graph: Graph;

View File

@ -1,6 +1,9 @@
import type { Node, NodeType } from "jsonc-parser";
import type { Graph, States } from "src/modules/GraphView/lib/jsonParser";
import { calculateNodeSize } from "src/modules/GraphView/lib/utils/calculateNodeSize";
import type {
Graph,
States,
} from "src/containers/Editor/components/views/GraphView/lib/jsonParser";
import { calculateNodeSize } from "src/containers/Editor/components/views/GraphView/lib/utils/calculateNodeSize";
import { addEdgeToGraph } from "./addEdgeToGraph";
import { addNodeToGraph } from "./addNodeToGraph";

View File

@ -1,11 +1,11 @@
import type { ViewPort } from "react-zoomable-ui/dist/ViewPort";
import type { CanvasDirection } from "reaflow/dist/layout/elkLayout";
import { create } from "zustand";
import { parser } from "src/modules/GraphView/lib/jsonParser";
import { getChildrenEdges } from "src/modules/GraphView/lib/utils/getChildrenEdges";
import { getOutgoers } from "src/modules/GraphView/lib/utils/getOutgoers";
import { parser } from "src/containers/Editor/components/views/GraphView/lib/jsonParser";
import { getChildrenEdges } from "src/containers/Editor/components/views/GraphView/lib/utils/getChildrenEdges";
import { getOutgoers } from "src/containers/Editor/components/views/GraphView/lib/utils/getOutgoers";
import type { NodeData, EdgeData } from "src/types/graph";
import useJson from "../../../store/useJson";
import useJson from "../../../../../../store/useJson";
export interface Graph {
viewPort: ViewPort | null;

View File

@ -1,7 +1,7 @@
import React from "react";
import type { DefaultTheme } from "styled-components";
import { useTheme } from "styled-components";
import { TextRenderer } from "src/modules/GraphView/CustomNode/TextRenderer";
import { TextRenderer } from "src/containers/Editor/components/views/GraphView/CustomNode/TextRenderer";
type TextColorFn = {
theme: DefaultTheme;

View File

@ -3,8 +3,8 @@ import dynamic from "next/dynamic";
import styled from "styled-components";
import { Allotment } from "allotment";
import "allotment/dist/style.css";
import useGraph from "src/modules/GraphView/stores/useGraph";
import { FullscreenDropzone } from "./FullscreenDropzone";
import useGraph from "src/containers/Editor/components/views/GraphView/stores/useGraph";
import { FullscreenDropzone } from "./components/FullscreenDropzone";
export const StyledEditor = styled(Allotment)`
position: relative !important;
@ -17,11 +17,11 @@ export const StyledEditor = styled(Allotment)`
}
`;
const TextEditor = dynamic(() => import("src/containers/Editor/TextEditor"), {
const TextEditor = dynamic(() => import("src/containers/Editor/components/TextEditor"), {
ssr: false,
});
const LiveEditor = dynamic(() => import("src/containers/Editor/LiveEditor"), {
const LiveEditor = dynamic(() => import("src/containers/Editor/components/LiveEditor"), {
ssr: false,
});

View File

@ -1,6 +1,6 @@
import React from "react";
import { Container, Title, Accordion } from "@mantine/core";
import Questions from "src/constants/faq.json";
import Questions from "src/data/faq.json";
export const FAQ = () => {
return (

View File

@ -2,7 +2,7 @@ import React from "react";
import { useRouter } from "next/router";
import type { ModalProps } from "@mantine/core";
import { Modal, Group, Button, Text, Divider } from "@mantine/core";
import { documentSvc } from "src/services/document.service";
import { documentSvc } from "src/lib/api/document.service";
import useJson from "src/store/useJson";
export const ClearModal = ({ opened, onClose }: ModalProps) => {

View File

@ -29,7 +29,7 @@ import { LuDownload } from "react-icons/lu";
import { SlOptionsVertical } from "react-icons/sl";
import { VscAdd, VscWarning } from "react-icons/vsc";
import type { FileFormat } from "src/enums/file.enum";
import { documentSvc } from "src/services/document.service";
import { documentSvc } from "src/lib/api/document.service";
import type { File } from "src/store/useFile";
import useFile from "src/store/useFile";
import useModal from "src/store/useModal";
@ -152,7 +152,9 @@ export const CloudModal = ({ opened, onClose }: ModalProps) => {
</Badge>
</Table.Td>
<Table.Td>{element.name}</Table.Td>
<Table.Td>{dayjs(element.updated_at).format("DD MMM YYYY")}</Table.Td>
<Table.Td fz="10" c="dimmed" title={dayjs(element.updated_at).format("DD MMM, YYYY")}>
{dayjs(element.updated_at).fromNow()}
</Table.Td>
<Table.Td onClick={e => e.stopPropagation()}>
<Flex gap="xs">
<Menu position="bottom" withArrow shadow="md">
@ -219,14 +221,7 @@ export const CloudModal = ({ opened, onClose }: ModalProps) => {
/>
<ScrollArea h="calc(100vh - 345px)" offsetScrollbars>
<LoadingOverlay visible={isLoading} />
<Table fz="xs" verticalSpacing="xs" highlightOnHover>
<Table.Thead>
<Table.Tr>
<Table.Th>Format</Table.Th>
<Table.Th>Name</Table.Th>
<Table.Th>Last opened date</Table.Th>
</Table.Tr>
</Table.Thead>
<Table fz="xs" verticalSpacing="4" highlightOnHover>
<Table.Tbody>{rows}</Table.Tbody>
</Table>
</ScrollArea>

View File

@ -4,7 +4,7 @@ import { Modal, Stack, Text, ScrollArea, Button } from "@mantine/core";
import { CodeHighlight } from "@mantine/code-highlight";
import { event as gaEvent } from "nextjs-google-analytics";
import { VscLock } from "react-icons/vsc";
import useGraph from "src/modules/GraphView/stores/useGraph";
import useGraph from "src/containers/Editor/components/views/GraphView/stores/useGraph";
import useModal from "src/store/useModal";
const dataToString = (data: any) => {

View File

@ -1,61 +0,0 @@
import React from "react";
import type { ModalProps } from "@mantine/core";
import { Button, Modal, Rating, Text, Textarea } from "@mantine/core";
import { toast } from "react-hot-toast";
import { supabase } from "src/lib/api/supabase";
export const ReviewModal = ({ opened, onClose }: ModalProps) => {
const [stars, setStars] = React.useState(0);
const [review, setReview] = React.useState("");
return (
<Modal
title="Leave a Review"
opened={opened}
onClose={() => {
onClose();
setStars(0);
setReview("");
}}
centered
>
<form
onSubmit={e => {
e.preventDefault();
if (stars === 0 && !review.length) return onClose();
supabase
.from("reviews")
.insert({ stars, review })
.then(({ error }) => {
if (error) return toast.error(error.message);
toast.success("Thank you for your feedback!");
});
onClose();
}}
>
<Text style={{ textAlign: "center" }}>How was your experience?</Text>
<Rating value={stars} onChange={setStars} my="lg" size="xl" mx="auto" />
<Textarea
placeholder="Please provide feedback on how we can enhance the product and let us know which features you require."
value={review}
onChange={e => setReview(e.currentTarget.value)}
minLength={10}
maxLength={500}
minRows={5}
maxRows={10}
autosize
/>
<Text fz={12} c="dimmed" style={{ textAlign: "right" }}>
500/{review.length}
</Text>
<Text fz={12}>
* Your feedback is kept anonymous. If you wish to be contacted, please provide your email
address along with your feedback.
</Text>
<Button type="submit" mt="lg" fullWidth>
Submit
</Button>
</form>
</Modal>
);
};

View File

@ -1,66 +0,0 @@
import React from "react";
import { useRouter } from "next/router";
import type { ModalProps } from "@mantine/core";
import {
TextInput,
Stack,
Modal,
Button,
CopyButton,
Tooltip,
ActionIcon,
Text,
} from "@mantine/core";
import { event as gaEvent } from "nextjs-google-analytics";
import { FiExternalLink } from "react-icons/fi";
import { MdCheck, MdCopyAll } from "react-icons/md";
export const ShareModal = ({ opened, onClose }: ModalProps) => {
const { query } = useRouter();
const shareURL = `https://jsoncrack.com/editor?json=${query.json}`;
return (
<Modal title="Create a Share Link" opened={opened} onClose={onClose} centered>
<Stack py="sm">
<Text fz="sm" fw={700}>
Share Link
</Text>
<TextInput
value={shareURL}
type="url"
readOnly
rightSection={
<CopyButton value={shareURL} timeout={2000}>
{({ copied, copy }) => (
<Tooltip label={copied ? "Copied" : "Copy"} withArrow position="right">
<ActionIcon
color={copied ? "teal" : "gray"}
onClick={() => {
copy();
gaEvent("copy_share_link");
}}
>
{copied ? <MdCheck size="1rem" /> : <MdCopyAll size="1rem" />}
</ActionIcon>
</Tooltip>
)}
</CopyButton>
}
/>
<Text fz="sm" fw={700}>
Embed into your website
</Text>
<Button
component="a"
color="green"
target="_blank"
href="/docs"
leftSection={<FiExternalLink />}
fullWidth
>
Learn How to Embed
</Button>
</Stack>
</Modal>
);
};

View File

@ -4,12 +4,10 @@ export { DownloadModal } from "./DownloadModal";
export { ImportModal } from "./ImportModal";
export { AccountModal } from "./AccountModal";
export { NodeModal } from "./NodeModal";
export { ShareModal } from "./ShareModal";
export { LoginModal } from "./LoginModal";
export { UpgradeModal } from "./UpgradeModal";
export { JWTModal } from "./JWTModal";
export { SchemaModal } from "./SchemaModal";
export { ReviewModal } from "./ReviewModal";
export { JQModal } from "./JQModal";
export { TypeModal } from "./TypeModal";
export { JPathModal } from "./JPathModal";
@ -21,12 +19,10 @@ type Modal =
| "import"
| "account"
| "node"
| "share"
| "login"
| "upgrade"
| "jwt"
| "schema"
| "review"
| "jq"
| "type"
| "jpath"

View File

@ -4,7 +4,7 @@ import { Menu, Avatar, Text } from "@mantine/core";
import { VscSignIn, VscFeedback, VscSignOut } from "react-icons/vsc";
import useModal from "src/store/useModal";
import useUser from "src/store/useUser";
import * as Styles from "./styles";
import { StyledToolElement } from "./styles";
export const AccountMenu = () => {
const user = useUser(state => state.user?.user_metadata);
@ -16,11 +16,11 @@ export const AccountMenu = () => {
return (
<Menu shadow="md" trigger="click" closeOnItemClick={false} withArrow>
<Menu.Target>
<Styles.StyledToolElement>
<StyledToolElement>
<Avatar color={user ? "teal" : "indigo"} variant="filled" size={20} radius="xl">
{user && "JC"}
</Avatar>
</Styles.StyledToolElement>
</StyledToolElement>
</Menu.Target>
<Menu.Dropdown>
{user ? (
@ -41,13 +41,11 @@ export const AccountMenu = () => {
{user && (
<>
<Menu.Divider />
<Menu.Item
leftSection={<VscFeedback />}
onClick={() => setVisible("review")(true)}
closeMenuOnClick
>
<Text size="xs">Feedback</Text>
</Menu.Item>
<Link href="https://github.com/AykutSarac/jsoncrack.com/discussions" target="_blank">
<Menu.Item leftSection={<VscFeedback />} closeMenuOnClick>
<Text size="xs">Feedback</Text>
</Menu.Item>
</Link>
<Menu.Item leftSection={<VscSignOut />} onClick={() => logout()} closeMenuOnClick>
<Text size="xs">Log out</Text>
</Menu.Item>

View File

@ -4,7 +4,7 @@ import { event as gaEvent } from "nextjs-google-analytics";
import { CgChevronDown } from "react-icons/cg";
import useFile from "src/store/useFile";
import useModal from "src/store/useModal";
import * as Styles from "./styles";
import { StyledToolElement } from "./styles";
export const FileMenu = () => {
const setVisible = useModal(state => state.setVisible);
@ -25,12 +25,12 @@ export const FileMenu = () => {
return (
<Menu shadow="md" withArrow>
<Menu.Target>
<Styles.StyledToolElement title="File">
<StyledToolElement title="File">
<Flex align="center" gap={3}>
File
<CgChevronDown />
</Flex>
</Styles.StyledToolElement>
</StyledToolElement>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item fz={12} onClick={() => setVisible("import")(true)}>

View File

@ -1,11 +1,11 @@
import React from "react";
import { JSONCrackLogo } from "src/layout/JsonCrackLogo";
import * as Styles from "./styles";
import { StyledToolElement } from "./styles";
export const Logo = () => {
return (
<Styles.StyledToolElement title="JSON Crack">
<StyledToolElement title="JSON Crack">
<JSONCrackLogo fontSize="1.2rem" hideText />
</Styles.StyledToolElement>
</StyledToolElement>
);
};

View File

@ -6,7 +6,7 @@ import { MdSettings } from "react-icons/md";
import { VscLock } from "react-icons/vsc";
import useConfig from "src/store/useConfig";
import useModal from "src/store/useModal";
import * as Styles from "./styles";
import { StyledToolElement } from "./styles";
export const OptionsMenu = () => {
const setVisible = useModal(state => state.setVisible);
@ -27,11 +27,11 @@ export const OptionsMenu = () => {
return (
<Menu shadow="md" trigger="click" closeOnItemClick={false} withArrow>
<Menu.Target>
<Styles.StyledToolElement>
<StyledToolElement>
<Flex gap={4}>
<MdSettings size="18" />
</Flex>
</Styles.StyledToolElement>
</StyledToolElement>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item

View File

@ -13,7 +13,7 @@ import { jsonToContent } from "src/lib/utils/jsonAdapter";
import useFile from "src/store/useFile";
import useJson from "src/store/useJson";
import useModal from "src/store/useModal";
import * as Styles from "./styles";
import { StyledToolElement } from "./styles";
export const ToolsMenu = () => {
const setVisible = useModal(state => state.setVisible);
@ -48,11 +48,11 @@ export const ToolsMenu = () => {
return (
<Menu shadow="md" withArrow>
<Menu.Target>
<Styles.StyledToolElement onClick={() => gaEvent("show_tools_menu")}>
<StyledToolElement onClick={() => gaEvent("show_tools_menu")}>
<Flex align="center" gap={3}>
Tools <CgChevronDown />
</Flex>
</Styles.StyledToolElement>
</StyledToolElement>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item

View File

@ -1,23 +1,36 @@
import React from "react";
import { Menu, Flex, Text, SegmentedControl } from "@mantine/core";
import { useHotkeys } from "@mantine/hooks";
import styled from "styled-components";
import { event as gaEvent } from "nextjs-google-analytics";
import toast from "react-hot-toast";
import { CgChevronDown } from "react-icons/cg";
import { TiFlowMerge } from "react-icons/ti";
import { VscExpandAll, VscCollapseAll, VscTarget } from "react-icons/vsc";
import useGraph from "src/containers/Editor/components/views/GraphView/stores/useGraph";
import { ViewMode } from "src/enums/viewMode.enum";
import useToggleHide from "src/hooks/useToggleHide";
import { getNextDirection } from "src/lib/utils/getNextDirection";
import useGraph from "src/modules/GraphView/stores/useGraph";
import useConfig from "src/store/useConfig";
import * as Styles from "./styles";
import type { LayoutDirection } from "src/types/graph";
import { StyledToolElement } from "./styles";
function rotateLayout(direction: "LEFT" | "RIGHT" | "DOWN" | "UP") {
const StyledFlowIcon = styled(TiFlowMerge)<{ rotate: number }>`
transform: rotate(${({ rotate }) => `${rotate}deg`});
`;
const getNextDirection = (direction: LayoutDirection) => {
if (direction === "RIGHT") return "DOWN";
if (direction === "DOWN") return "LEFT";
if (direction === "LEFT") return "UP";
return "RIGHT";
};
const rotateLayout = (direction: LayoutDirection) => {
if (direction === "LEFT") return 90;
if (direction === "UP") return 180;
if (direction === "RIGHT") return 270;
return 360;
}
};
export const ViewMenu = () => {
const { validateHiddenNodes } = useToggleHide();
@ -65,11 +78,11 @@ export const ViewMenu = () => {
return (
<Menu shadow="md" closeOnItemClick={false} withArrow>
<Menu.Target>
<Styles.StyledToolElement onClick={() => gaEvent("show_view_menu")}>
<StyledToolElement onClick={() => gaEvent("show_view_menu")}>
<Flex align="center" gap={3}>
View <CgChevronDown />
</Flex>
</Styles.StyledToolElement>
</StyledToolElement>
</Menu.Target>
<Menu.Dropdown>
<SegmentedControl
@ -95,7 +108,7 @@ export const ViewMenu = () => {
toggleDirection();
gaEvent("rotate_layout", { label: direction });
}}
leftSection={<Styles.StyledFlowIcon rotate={rotateLayout(direction || "RIGHT")} />}
leftSection={<StyledFlowIcon rotate={rotateLayout(direction || "RIGHT")} />}
rightSection={
<Text ml="md" fz={10} c="dimmed">
{coreKey} Shift D

View File

@ -3,8 +3,8 @@ import { Menu, Flex, Input, Text } from "@mantine/core";
import { getHotkeyHandler, useHotkeys } from "@mantine/hooks";
import { event as gaEvent } from "nextjs-google-analytics";
import { CgChevronDown } from "react-icons/cg";
import useGraph from "src/modules/GraphView/stores/useGraph";
import * as Styles from "./styles";
import useGraph from "src/containers/Editor/components/views/GraphView/stores/useGraph";
import { StyledToolElement } from "./styles";
export const ZoomMenu = () => {
const zoomIn = useGraph(state => state.zoomIn);
@ -27,12 +27,12 @@ export const ZoomMenu = () => {
return (
<Menu shadow="md" trigger="click" closeOnItemClick={false} withArrow>
<Menu.Target>
<Styles.StyledToolElement onClick={() => gaEvent("show_zoom_menu")}>
<StyledToolElement onClick={() => gaEvent("show_zoom_menu")}>
<Flex gap={4} align="center">
{Math.round(zoomFactor * 100)}%
<CgChevronDown />
</Flex>
</Styles.StyledToolElement>
</StyledToolElement>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item>

View File

@ -1,5 +1,6 @@
import React from "react";
import { Text, Flex, Group, Select, Image } from "@mantine/core";
import styled from "styled-components";
import toast from "react-hot-toast";
import { AiOutlineFullscreen } from "react-icons/ai";
import { FiDownload } from "react-icons/fi";
@ -16,7 +17,26 @@ import { OptionsMenu } from "./OptionsMenu";
import { ToolsMenu } from "./ToolsMenu";
import { ViewMenu } from "./ViewMenu";
import { ZoomMenu } from "./ZoomMenu";
import * as Styles from "./styles";
import { StyledToolElement } from "./styles";
const StyledTools = styled.div`
position: relative;
display: flex;
width: 100%;
align-items: center;
gap: 4px;
justify-content: space-between;
height: 40px;
padding: 4px 8px;
background: ${({ theme }) => theme.TOOLBAR_BG};
color: ${({ theme }) => theme.SILVER};
z-index: 36;
border-bottom: 1px solid ${({ theme }) => theme.SILVER_DARK};
@media only screen and (max-width: 320px) {
display: none;
}
`;
function fullscreenBrowser() {
if (!document.fullscreenElement) {
@ -39,15 +59,15 @@ export const Toolbar = ({ isWidget = false }: ToolbarProps) => {
const isAuthenticated = useUser(state => state.isAuthenticated);
return (
<Styles.StyledTools>
<StyledTools>
{isWidget && <Logo />}
{!isWidget && (
<Group gap="xs" justify="left" w="100%" style={{ flexWrap: "nowrap" }}>
<Styles.StyledToolElement title="JSON Crack">
<StyledToolElement title="JSON Crack">
<Flex gap="xs" align="center" justify="center">
<JSONCrackLogo fontSize="0.8rem" hideLogo />
</Flex>
</Styles.StyledToolElement>
</StyledToolElement>
<Select
defaultValue="json"
@ -70,48 +90,39 @@ export const Toolbar = ({ isWidget = false }: ToolbarProps) => {
<ViewMenu />
<ToolsMenu />
{isAuthenticated && (
<Styles.StyledToolElement title="Cloud" onClick={() => setVisible("cloud")(true)}>
<StyledToolElement title="Cloud" onClick={() => setVisible("cloud")(true)}>
Cloud
</Styles.StyledToolElement>
</StyledToolElement>
)}
</Group>
)}
<Group gap="xs" justify="right" w="100%" style={{ flexWrap: "nowrap" }}>
{!isWidget && (
<Styles.StyledToolElement
onClick={() => window.open("https://todiagram.com?ref=jsoncrack.com")}
>
<StyledToolElement onClick={() => window.open("https://todiagram.com?ref=jsoncrack.com")}>
<Flex align="center" gap="4">
<Image src="https://todiagram.com/logo.svg" alt="ToDiagram" width={14} height={14} />
<Text c="bright" fw={600} fz="xs">
ToDiagram
</Text>
</Flex>
</Styles.StyledToolElement>
</StyledToolElement>
)}
<SearchInput />
{!isWidget && (
<>
<Styles.StyledToolElement
title="Save as Image"
onClick={() => setVisible("download")(true)}
>
<StyledToolElement title="Save as Image" onClick={() => setVisible("download")(true)}>
<FiDownload size="18" />
</Styles.StyledToolElement>
</StyledToolElement>
<ZoomMenu />
<AccountMenu />
<OptionsMenu />
<Styles.StyledToolElement
title="Fullscreen"
$hide={isWidget}
onClick={fullscreenBrowser}
>
<StyledToolElement title="Fullscreen" $hide={isWidget} onClick={fullscreenBrowser}>
<AiOutlineFullscreen size="18" />
</Styles.StyledToolElement>
</StyledToolElement>
</>
)}
</Group>
</Styles.StyledTools>
</StyledTools>
);
};

View File

@ -1,24 +1,4 @@
import styled from "styled-components";
import { TiFlowMerge } from "react-icons/ti";
export const StyledTools = styled.div`
position: relative;
display: flex;
width: 100%;
align-items: center;
gap: 4px;
justify-content: space-between;
height: 40px;
padding: 4px 8px;
background: ${({ theme }) => theme.TOOLBAR_BG};
color: ${({ theme }) => theme.SILVER};
z-index: 36;
border-bottom: 1px solid ${({ theme }) => theme.SILVER_DARK};
@media only screen and (max-width: 320px) {
display: none;
}
`;
export const StyledToolElement = styled.button<{ $hide?: boolean }>`
display: ${({ $hide }) => ($hide ? "none" : "flex")};
@ -42,7 +22,3 @@ export const StyledToolElement = styled.button<{ $hide?: boolean }>`
box-shadow: none;
}
`;
export const StyledFlowIcon = styled(TiFlowMerge)<{ rotate: number }>`
transform: rotate(${({ rotate }) => `${rotate}deg`});
`;

View File

@ -1,8 +1,8 @@
import React from "react";
import { useDebouncedValue } from "@mantine/hooks";
import { event as gaEvent } from "nextjs-google-analytics";
import useGraph from "src/containers/Editor/components/views/GraphView/stores/useGraph";
import { searchQuery, cleanupHighlight, highlightMatchedNodes } from "src/lib/utils/search";
import useGraph from "src/modules/GraphView/stores/useGraph";
export const useFocusNode = () => {
const viewPort = useGraph(state => state.viewPort);

View File

@ -1,5 +1,5 @@
import React from "react";
import useGraph from "src/modules/GraphView/stores/useGraph";
import useGraph from "src/containers/Editor/components/views/GraphView/stores/useGraph";
const useToggleHide = () => {
const getCollapsedNodeIds = useGraph(state => state.getCollapsedNodeIds);

View File

@ -13,11 +13,9 @@ const modalComponents: ModalComponent[] = [
{ key: "account", component: Modals.AccountModal },
{ key: "upgrade", component: Modals.UpgradeModal },
{ key: "login", component: Modals.LoginModal },
{ key: "share", component: Modals.ShareModal },
{ key: "jwt", component: Modals.JWTModal },
{ key: "node", component: Modals.NodeModal },
{ key: "schema", component: Modals.SchemaModal },
{ key: "review", component: Modals.ReviewModal },
{ key: "jq", component: Modals.JQModal },
{ key: "type", component: Modals.TypeModal },
{ key: "jpath", component: Modals.JPathModal },

View File

@ -1,6 +0,0 @@
export function getNextDirection(direction: "LEFT" | "RIGHT" | "DOWN" | "UP") {
if (direction === "RIGHT") return "DOWN";
if (direction === "DOWN") return "LEFT";
if (direction === "LEFT") return "UP";
return "RIGHT";
}

View File

@ -1,42 +0,0 @@
The jsoncrack.com Commercial License (the “Commercial License”)
Copyright (c) 2024-present jsoncrack.com
With regard to the jsoncrack.com Software:
This software and associated documentation files (the "Software") may only be
used in production, if you (and any entity that you represent) have agreed to,
and are in compliance with, the jsoncrack.com Subscription Terms available
at https://jsoncrack.com/legal/terms, or other agreements governing
the use of the Software, as mutually agreed by you and jsoncrack.com ("JSON Crack"),
and otherwise have a valid jsoncrack.com Enterprise Edition subscription ("Commercial Subscription")
for the correct number of hosts as defined in the "Commercial Terms ("Hosts"). Subject to the foregoing sentence,
you are free to modify this Software and publish patches to the Software. You agree
that jsoncrack.com and/or its licensors (as applicable) retain all right, title and interest in
and to all such modifications and/or patches, and all such modifications and/or
patches may only be used, copied, modified, displayed, distributed, or otherwise
exploited with a valid Commercial Subscription for the correct number of hosts.
Notwithstanding the foregoing, you may copy and modify the Software for development
and testing purposes, without requiring a subscription. You agree that jsoncrack.com and/or
its licensors (as applicable) retain all right, title and interest in and to all such
modifications. You are not granted any other rights beyond what is expressly stated herein.
Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
and/or sell the Software.
This Commercial License applies only to the part of this Software that is not distributed under
the AGPLv3 license. Any part of this Software distributed under the MIT license or which
is served client-side as an image, font, cascading stylesheet (CSS), file which produces
or is compiled, arranged, augmented, or combined into client-side JavaScript, in whole or
in part, is copyrighted under the AGPLv3 license. The full text of this Commercial License shall
be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
For all third party components incorporated into the jsoncrack.com Software, those
components are licensed under the original license provided by the owner of the
applicable component.

View File

@ -9,7 +9,7 @@ import { NextSeo } from "next-seo";
import { SEO } from "src/constants/seo";
import { darkTheme, lightTheme } from "src/constants/theme";
import { Editor } from "src/containers/Editor";
import { BottomBar } from "src/containers/Editor/BottomBar";
import { BottomBar } from "src/containers/Editor/components/BottomBar";
import { Toolbar } from "src/containers/Toolbar";
import useConfig from "src/store/useConfig";
import useFile from "src/store/useFile";

View File

@ -16,7 +16,7 @@ import {
import { NextSeo } from "next-seo";
import { MdErrorOutline } from "react-icons/md";
import { SEO } from "src/constants/seo";
import { AuthLayout } from "src/containers/AuthLayout";
import { AuthLayout } from "src/layout/AuthLayout";
import { supabase } from "src/lib/api/supabase";
function ResetPassword() {

View File

@ -2,8 +2,8 @@ import React from "react";
import { Box, Container, Paper, Stack, Text, Title } from "@mantine/core";
import { NextSeo } from "next-seo";
import { SEO } from "src/constants/seo";
import privacy from "src/data/privacy.json";
import Layout from "src/layout/Layout";
import privacy from "../../constants/privacy.json";
const Privacy = () => {
return (

View File

@ -2,8 +2,8 @@ import React from "react";
import { Box, Container, Paper, Stack, Text, Title } from "@mantine/core";
import { NextSeo } from "next-seo";
import { SEO } from "src/constants/seo";
import terms from "src/data/terms.json";
import Layout from "src/layout/Layout";
import terms from "../../constants/terms.json";
const Terms = () => {
return (

View File

@ -19,7 +19,7 @@ import { AiOutlineGithub } from "react-icons/ai";
import { FcGoogle } from "react-icons/fc";
import { MdErrorOutline } from "react-icons/md";
import { SEO } from "src/constants/seo";
import { AuthLayout } from "src/containers/AuthLayout";
import { AuthLayout } from "src/layout/AuthLayout";
import { supabase } from "src/lib/api/supabase";
import useUser from "src/store/useUser";

View File

@ -4,7 +4,7 @@ import { useRouter } from "next/router";
import { Anchor, Button, Center, Text } from "@mantine/core";
import { NextSeo } from "next-seo";
import { SEO } from "src/constants/seo";
import { AuthLayout } from "src/containers/AuthLayout";
import { AuthLayout } from "src/layout/AuthLayout";
import { supabase } from "src/lib/api/supabase";
const SignUp = () => {

View File

@ -6,23 +6,27 @@ import { ThemeProvider } from "styled-components";
import { NextSeo } from "next-seo";
import toast from "react-hot-toast";
import { darkTheme, lightTheme } from "src/constants/theme";
import useGraph from "src/containers/Editor/components/views/GraphView/stores/useGraph";
import { Toolbar } from "src/containers/Toolbar";
import useGraph from "src/modules/GraphView/stores/useGraph";
import useFile from "src/store/useFile";
import type { LayoutDirection } from "src/types/graph";
interface EmbedMessage {
data: {
json?: string;
options?: {
theme?: "light" | "dark";
direction?: "LEFT" | "RIGHT" | "DOWN" | "UP";
direction?: LayoutDirection;
};
};
}
const GraphView = dynamic(() => import("src/modules/GraphView").then(c => c.GraphView), {
ssr: false,
});
const GraphView = dynamic(
() => import("src/containers/Editor/components/views/GraphView").then(c => c.GraphView),
{
ssr: false,
}
);
const WidgetPage = () => {
const { query, push, isReady } = useRouter();

View File

@ -1,7 +1,7 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { ViewMode } from "src/enums/viewMode.enum";
import useGraph from "../modules/GraphView/stores/useGraph";
import useGraph from "../containers/Editor/components/views/GraphView/stores/useGraph";
const initialStates = {
darkmodeEnabled: false,

View File

@ -2,15 +2,52 @@ import debounce from "lodash.debounce";
import { event as gaEvent } from "nextjs-google-analytics";
import { toast } from "react-hot-toast";
import { create } from "zustand";
import { defaultJson } from "src/constants/data";
import { FileFormat } from "src/enums/file.enum";
import { documentSvc } from "src/lib/api/document.service";
import { isIframe } from "src/lib/utils/helpers";
import { contentToJson, jsonToContent } from "src/lib/utils/jsonAdapter";
import { isIframe } from "src/lib/utils/widget";
import { documentSvc } from "src/services/document.service";
import useGraph from "../modules/GraphView/stores/useGraph";
import useGraph from "../containers/Editor/components/views/GraphView/stores/useGraph";
import useConfig from "./useConfig";
import useJson from "./useJson";
const defaultJson = JSON.stringify(
{
squadName: "Super hero squad",
homeTown: "Metro City",
formed: 2016,
secretBase: "Super tower",
active: true,
members: [
{
name: "Molecule Man",
age: 29,
secretIdentity: "Dan Jukes",
powers: ["Radiation resistance", "Turning tiny", "Radiation blast"],
},
{
name: "Madame Uppercut",
age: 39,
secretIdentity: "Jane Wilson",
powers: ["Million tonne punch", "Damage resistance", "Superhuman reflexes"],
},
{
name: "Eternal Flame",
age: 1000000,
secretIdentity: "Unknown",
powers: [
"Immortality",
"Heat Immunity",
"Inferno",
"Teleportation",
"Interdimensional travel",
],
},
],
},
null,
2
);
type SetContents = {
contents?: string;
hasChanges?: boolean;

View File

@ -1,5 +1,5 @@
import { create } from "zustand";
import useGraph from "src/modules/GraphView/stores/useGraph";
import useGraph from "src/containers/Editor/components/views/GraphView/stores/useGraph";
interface JsonActions {
setJson: (json: string) => void;

View File

@ -16,12 +16,10 @@ const initialStates: ModalState = {
import: false,
account: false,
node: false,
share: false,
login: false,
upgrade: false,
jwt: false,
schema: false,
review: false,
jq: false,
type: false,
jpath: false,

View File

@ -19,3 +19,5 @@ export interface EdgeData {
from: string;
to: string;
}
export type LayoutDirection = "LEFT" | "RIGHT" | "DOWN" | "UP";