From bdb93a85b145850aab5227a1a87e667b3fa620a6 Mon Sep 17 00:00:00 2001 From: Theo Gainey Date: Mon, 3 Oct 2022 09:09:42 -0400 Subject: [PATCH 1/3] feature: edit node in modal - modify NodeModal to be editable - Edits that are Valid JSON will be saved to graph nodes Changes to be committed: modified: src/components/Graph/index.tsx modified: src/containers/Editor/LiveEditor/GraphCanvas.tsx modified: src/containers/Modals/NodeModal/index.tsx --- src/components/Graph/index.tsx | 4 +-- .../Editor/LiveEditor/GraphCanvas.tsx | 2 +- src/containers/Modals/NodeModal/index.tsx | 32 +++++++++++++++---- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/components/Graph/index.tsx b/src/components/Graph/index.tsx index 61c108a021e..54462d55281 100644 --- a/src/components/Graph/index.tsx +++ b/src/components/Graph/index.tsx @@ -15,7 +15,7 @@ import { ErrorView } from "./ErrorView"; interface LayoutProps { isWidget?: boolean; openModal: () => void; - setSelectedNode: (node: [string, string][]) => void; + setSelectedNode: (node: NodeData) => void; } const StyledEditorWrapper = styled.div<{ isWidget: boolean }>` @@ -61,7 +61,7 @@ const GraphComponent = ({ const handleNodeClick = React.useCallback( (e: React.MouseEvent, data: NodeData) => { - if (setSelectedNode) setSelectedNode(data.text); + if (setSelectedNode) setSelectedNode(data); if (openModal) openModal(); }, [openModal, setSelectedNode] diff --git a/src/containers/Editor/LiveEditor/GraphCanvas.tsx b/src/containers/Editor/LiveEditor/GraphCanvas.tsx index 6ecb3c2520e..5f31612710c 100644 --- a/src/containers/Editor/LiveEditor/GraphCanvas.tsx +++ b/src/containers/Editor/LiveEditor/GraphCanvas.tsx @@ -5,7 +5,7 @@ import useGraph from "src/hooks/store/useGraph"; export const GraphCanvas = ({ isWidget = false }: { isWidget?: boolean }) => { const [isModalVisible, setModalVisible] = React.useState(false); - const [selectedNode, setSelectedNode] = React.useState<[string, string][]>([]); + const [selectedNode, setSelectedNode] = React.useState({} as NodeData); const openModal = React.useCallback(() => setModalVisible(true), []); diff --git a/src/containers/Modals/NodeModal/index.tsx b/src/containers/Modals/NodeModal/index.tsx index f34066ee2a0..73d5330d985 100644 --- a/src/containers/Modals/NodeModal/index.tsx +++ b/src/containers/Modals/NodeModal/index.tsx @@ -3,10 +3,13 @@ import toast from "react-hot-toast"; import { FiCopy } from "react-icons/fi"; import { Button } from "src/components/Button"; import { Modal } from "src/components/Modal"; +import useGraph from "src/hooks/store/useGraph"; import styled from "styled-components"; +import { parser } from "src/utils/jsonParser"; +import { isValidJson } from "src/utils/isValidJson"; interface NodeModalProps { - selectedNode: object; + selectedNode: NodeData; visible: boolean; closeModal: () => void; } @@ -26,15 +29,30 @@ const StyledTextarea = styled.textarea` `; export const NodeModal = ({ selectedNode, visible, closeModal }: NodeModalProps) => { - const nodeData = Array.isArray(selectedNode) - ? Object.fromEntries(selectedNode) - : selectedNode; - + const setGraphValue = useGraph(state => state.setGraphValue); + const nodes = useGraph(state => state.nodes); + const nodeData = Array.isArray(selectedNode.text) + ? Object.fromEntries(selectedNode.text) + : selectedNode.text; + const [text, setText] = React.useState(nodeData); + const handleClipboard = () => { toast.success("Content copied to clipboard!"); - navigator.clipboard.writeText(JSON.stringify(nodeData)); + navigator.clipboard.writeText(JSON.stringify(text)); closeModal(); }; + + const update = (newText:string) => { + if(isValidJson(newText)){ + const parsedNode = parser(newText); + const parsedText = parsedNode?.nodes[0]?.text; + Array.isArray(parsedText) + ? setText(Object.fromEntries(parsedText)) + : setText(parsedText); + const updatedNode = {...selectedNode, text: parsedText} + setGraphValue("nodes", [...nodes.filter(({id}) => id !== selectedNode.id), updatedNode]); + }; + }; return ( @@ -49,7 +67,7 @@ export const NodeModal = ({ selectedNode, visible, closeModal }: NodeModalProps) }, 2 )} - readOnly + onChange={(e)=> update(e.target.value)} /> From db6b4876e4891a0f016a8ca8354fe1c36235c80f Mon Sep 17 00:00:00 2001 From: Theo Gainey Date: Mon, 3 Oct 2022 20:26:37 -0400 Subject: [PATCH 2/3] feature: NodeModal ErrorContainer - add ErrorContainer to NodeModal Changes to be committed: modified: src/containers/Modals/NodeModal/index.tsx --- src/containers/Modals/NodeModal/index.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/containers/Modals/NodeModal/index.tsx b/src/containers/Modals/NodeModal/index.tsx index 73d5330d985..09c9e02d338 100644 --- a/src/containers/Modals/NodeModal/index.tsx +++ b/src/containers/Modals/NodeModal/index.tsx @@ -7,6 +7,7 @@ import useGraph from "src/hooks/store/useGraph"; import styled from "styled-components"; import { parser } from "src/utils/jsonParser"; import { isValidJson } from "src/utils/isValidJson"; +import { ErrorContainer } from "src/components/ErrorContainer"; interface NodeModalProps { selectedNode: NodeData; @@ -35,7 +36,8 @@ export const NodeModal = ({ selectedNode, visible, closeModal }: NodeModalProps) ? Object.fromEntries(selectedNode.text) : selectedNode.text; const [text, setText] = React.useState(nodeData); - + const [error, hasError] = React.useState(false); + const handleClipboard = () => { toast.success("Content copied to clipboard!"); navigator.clipboard.writeText(JSON.stringify(text)); @@ -49,8 +51,12 @@ export const NodeModal = ({ selectedNode, visible, closeModal }: NodeModalProps) Array.isArray(parsedText) ? setText(Object.fromEntries(parsedText)) : setText(parsedText); - const updatedNode = {...selectedNode, text: parsedText} - setGraphValue("nodes", [...nodes.filter(({id}) => id !== selectedNode.id), updatedNode]); + const updatedNode = {...selectedNode, text: parsedText} ; + setGraphValue("nodes", [...nodes.filter(({id}) => id !== selectedNode.id), updatedNode]); + hasError(false); + } + else { + hasError(true); }; }; @@ -58,6 +64,7 @@ export const NodeModal = ({ selectedNode, visible, closeModal }: NodeModalProps) Node Content + Date: Tue, 4 Oct 2022 22:34:07 -0400 Subject: [PATCH 3/3] feature: Edit JSON in Diagram View - add graphParser to parse graph Nodes into JSON - add functionality to update JSON on node edit References Issue #147 Changes to be committed: modified: src/containers/Modals/NodeModal/index.tsx new file: src/utils/graphParser.ts --- src/containers/Modals/NodeModal/index.tsx | 13 +++++-- src/utils/graphParser.ts | 44 +++++++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 src/utils/graphParser.ts diff --git a/src/containers/Modals/NodeModal/index.tsx b/src/containers/Modals/NodeModal/index.tsx index 09c9e02d338..0ce72411d44 100644 --- a/src/containers/Modals/NodeModal/index.tsx +++ b/src/containers/Modals/NodeModal/index.tsx @@ -8,6 +8,8 @@ import styled from "styled-components"; import { parser } from "src/utils/jsonParser"; import { isValidJson } from "src/utils/isValidJson"; import { ErrorContainer } from "src/components/ErrorContainer"; +import useConfig from "src/hooks/store/useConfig"; +import { graphParser } from "src/utils/graphParser"; interface NodeModalProps { selectedNode: NodeData; @@ -32,6 +34,8 @@ const StyledTextarea = styled.textarea` export const NodeModal = ({ selectedNode, visible, closeModal }: NodeModalProps) => { const setGraphValue = useGraph(state => state.setGraphValue); const nodes = useGraph(state => state.nodes); + const edges = useGraph(state => state.edges); + const setJson = useConfig(state => state.setJson); const nodeData = Array.isArray(selectedNode.text) ? Object.fromEntries(selectedNode.text) : selectedNode.text; @@ -51,9 +55,12 @@ export const NodeModal = ({ selectedNode, visible, closeModal }: NodeModalProps) Array.isArray(parsedText) ? setText(Object.fromEntries(parsedText)) : setText(parsedText); - const updatedNode = {...selectedNode, text: parsedText} ; - setGraphValue("nodes", [...nodes.filter(({id}) => id !== selectedNode.id), updatedNode]); - hasError(false); + const updatedNode = {...selectedNode, text: parsedText}; + const newNodes = nodes.map((node)=> node.id===selectedNode.id ? updatedNode : node); + + setGraphValue("nodes", newNodes); + hasError(false); + setJson(JSON.stringify(graphParser(newNodes, edges))); } else { hasError(true); diff --git a/src/utils/graphParser.ts b/src/utils/graphParser.ts new file mode 100644 index 00000000000..fec397d8b51 --- /dev/null +++ b/src/utils/graphParser.ts @@ -0,0 +1,44 @@ + +const nodeToValue = (node:NodeData) => Array.isArray(node.text) ? Object.fromEntries(node.text) : node.text; + +const hasEdgeLeaving = (node:NodeData, edges:EdgeData[]) => edges.some((edge)=> node.id === edge.from); +const hasChild = (node:NodeData) => !!node.data.isParent; + +const getChildNode = (edge: EdgeData, nodes: NodeData[]) => nodes.find((node)=> node.id === edge.to) as NodeData; +const moveNodeToFront = (node: NodeData, nodes: NodeData[]) => [node, ...removeNode(node, nodes)]; + +const removeNode = (node: NodeData, nodes: NodeData[]) => nodes.filter((n)=> n.id !== node.id); +const removeEdge = (edge: EdgeData, edges: EdgeData[]) => edges.filter((e)=> e.id !== edge.id); + +const getEdge = (node:NodeData, edges:EdgeData[]) => edges.find((edge)=> node.id === edge.from) as EdgeData; + +export const graphParser = (nodes:NodeData[], edges:EdgeData[]) =>{ + const currentNode = nodes[0]; + if((!hasChild(currentNode) && !hasEdgeLeaving(currentNode, edges))){ + return nodeToValue(currentNode); + } + + const remainingNodes = removeNode(currentNode, nodes); + + if(hasChild(currentNode)) { + const childEdges = edges.filter((edge)=> edge.from === currentNode.id); + const remainingEdges = edges.filter((edge)=> edge.from !== currentNode.id); + const key = nodeToValue(currentNode); + const obj = {}; + obj[key] = childEdges.map((edge)=>{ + const childNode = getChildNode(edge, remainingNodes); + const nextNodes = moveNodeToFront(childNode, remainingNodes); + return typeof nodeToValue(childNode) === 'string' + ? nodeToValue(childNode) + : Object.assign({}, nodeToValue(childNode), graphParser(nextNodes, remainingEdges)); + }); + return obj; + } + const nextEdge = getEdge(currentNode, edges); + const remainingEdges = removeEdge(nextEdge, edges); + const childNode = getChildNode(nextEdge, remainingNodes); + const nextNodes = moveNodeToFront(childNode, remainingNodes); + + return Object.assign({}, nodeToValue(currentNode), graphParser(nextNodes, remainingEdges)); +} +