From 2143be74fc1d756a188540934b405516d78f5d0f Mon Sep 17 00:00:00 2001 From: AykutSarac Date: Sat, 27 Aug 2022 12:36:28 +0300 Subject: [PATCH] implement collapse/expand --- src/components/CustomNode/TextNode.tsx | 7 +- src/components/CustomNode/index.tsx | 3 +- src/components/Graph/index.tsx | 46 ++------ src/hooks/store/useGraph.tsx | 139 +++++++++++++++++++++++++ src/utils/findEdgeChildren.ts | 24 +++-- 5 files changed, 170 insertions(+), 49 deletions(-) create mode 100644 src/hooks/store/useGraph.tsx diff --git a/src/components/CustomNode/TextNode.tsx b/src/components/CustomNode/TextNode.tsx index a25bc59..0f6b19c 100644 --- a/src/components/CustomNode/TextNode.tsx +++ b/src/components/CustomNode/TextNode.tsx @@ -1,7 +1,9 @@ import React from "react"; import { BiChevronLeft, BiChevronRight } from "react-icons/bi"; +import { NodeData, EdgeData } from "reaflow"; import { ConditionalWrapper, CustomNodeProps } from "src/components/CustomNode"; import useConfig from "src/hooks/store/useConfig"; +import useGraph from "src/hooks/store/useGraph"; import styled from "styled-components"; import * as Styled from "./styles"; @@ -23,7 +25,8 @@ const StyledExpand = styled.button` border-left: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT}; `; -const TextNode: React.FC> = ({ +const TextNode: React.FC & { node: NodeData }> = ({ + node, width, height, value, @@ -34,6 +37,7 @@ const TextNode: React.FC> = ({ const performanceMode = useConfig((state) => state.performanceMode); const expand = useConfig((state) => state.expand); const [isExpanded, setIsExpanded] = React.useState(!expand); + const toggleNode = useGraph((state) => state.toggleNode); React.useEffect(() => { setIsExpanded(expand); @@ -41,6 +45,7 @@ const TextNode: React.FC> = ({ const handleExpand = (e: React.MouseEvent) => { e.stopPropagation(); + toggleNode(node, isExpanded); setIsExpanded(!isExpanded); }; diff --git a/src/components/CustomNode/index.tsx b/src/components/CustomNode/index.tsx index 945f127..4c7d7c9 100644 --- a/src/components/CustomNode/index.tsx +++ b/src/components/CustomNode/index.tsx @@ -37,7 +37,7 @@ export const CustomNode = (nodeProps: NodeProps) => { return ( }> - {({ width, height, x, y }) => { + {({ width, height, x, y, node }) => { if (properties.text instanceof Object) { const entries = Object.entries(properties.text); @@ -54,6 +54,7 @@ export const CustomNode = (nodeProps: NodeProps) => { return ( ` const MemoizedGraph = React.memo(function Layout({ isWidget }: LayoutProps) { const json = useConfig((state) => state.json); - - const [nodes, edges, newNodes, newEdges, selectedNode] = useNodeTools( - (state) => [ - state.nodes, - state.edges, - state.newNodes, - state.newEdges, - state.selectedNode, - ], - shallow - ); - const setNodeTools = useNodeTools((state) => state.setNodeTools); + const nodes = useGraph((state) => state.nodes); + const edges = useGraph((state) => state.edges); + const setGraphValue = useGraph((state) => state.setGraphValue); const [size, setSize] = React.useState({ width: 2000, @@ -61,11 +53,11 @@ const MemoizedGraph = React.memo(function Layout({ isWidget }: LayoutProps) { React.useEffect(() => { const { nodes, edges } = getEdgeNodes(json, expand); - setNodeTools("nodes", nodes); - setNodeTools("edges", edges); - setNodeTools("newNodes", nodes); - setNodeTools("newEdges", edges); - }, [json, expand, setNodeTools]); + setGraphValue("nodes", nodes); + setGraphValue("edges", edges); + setGraphValue("initialNodes", nodes); + setGraphValue("initialEdges", edges); + }, [expand, json, setGraphValue]); const onInit = (ref: ReactZoomPanPinchRef) => { setConfig("zoomPanPinch", ref); @@ -76,20 +68,6 @@ const MemoizedGraph = React.memo(function Layout({ isWidget }: LayoutProps) { setSize({ width: layout.width, height: layout.height }); }; - const { selections, onClick, removeSelection } = useSelection({ - nodes, - edges, - onSelection: (s) => { - if (!isWidget) { - if (s[0] === selectedNode) { - removeSelection(selectedNode); - } else { - setNodeTools("selectedNode", s[0]); - } - } - }, - }); - return ( } - edge={(props) => - newEdges.find((e) => e.id === props.id) ? : <> - } zoomable={false} readonly /> diff --git a/src/hooks/store/useGraph.tsx b/src/hooks/store/useGraph.tsx new file mode 100644 index 0000000..d9df31f --- /dev/null +++ b/src/hooks/store/useGraph.tsx @@ -0,0 +1,139 @@ +import create from "zustand"; +import { EdgeData, NodeData } from "reaflow"; +import { Graph } from "src/components/Graph"; +import { findEdgeChildren } from "src/utils/findEdgeChildren"; +import { findNodeChildren } from "src/utils/findNodeChildren"; + +export interface Graph { + initialNodes: NodeData[]; + initialEdges: EdgeData[]; + nodes: NodeData[]; + edges: EdgeData[]; + collapsedNodes: { [key: string]: NodeData[] }; + collapsedEdges: { [key: string]: EdgeData[] }; +} + +interface GraphActions { + setGraphValue: (key: keyof Graph, value: any) => void; + toggleNode: (node: NodeData, isExpanded: boolean) => void; +} + +function isNode(element: NodeData | EdgeData) { + if ("text" in element) return true; + return false; +} + +export const getIncomers = ( + node: NodeData, + nodes: NodeData[], + edges: EdgeData[] +): NodeData[] => { + if (!isNode(node)) { + return []; + } + + const incomersIds = edges.filter((e) => e.to === node.id).map((e) => e.from); + return nodes.filter((n) => incomersIds.includes(n.id)); +}; + +export const getOutgoingEdges = ( + node: NodeData, + edges: EdgeData[] +): EdgeData[] => { + if (!isNode(node)) { + return []; + } + + return edges.filter((edge) => edge.from === node.id); +}; + +export const getIncomingEdges = ( + node: NodeData, + edges: EdgeData[] +): EdgeData[] => { + if (!isNode(node)) { + return []; + } + + return edges.filter((edge) => edge.to === node.id); +}; + +export const getOutgoers = ( + node: NodeData, + nodes: NodeData[], + edges: EdgeData[] +) => { + const allOutgoers: NodeData[] = []; + + if (!isNode(node)) { + return []; + } + + const runner = (currentNode: NodeData) => { + const outgoerIds = edges + .filter((e) => e.from === currentNode.id) + .map((e) => e.to); + const nodeList = nodes.filter((n) => outgoerIds.includes(n.id)); + allOutgoers.push(...nodeList); + nodeList.forEach((node) => runner(node)); + }; + + return allOutgoers; +}; + +const initialStates: Graph = { + initialNodes: [], + initialEdges: [], + nodes: [], + edges: [], + collapsedNodes: {}, + collapsedEdges: {}, +}; + +const useGraph = create((set, get) => ({ + ...initialStates, + setGraphValue: (key, value) => + set((state) => ({ + ...state, + [key]: value, + })), + toggleNode: (node, isExpanded) => + set((state) => { + const childrenNodes = findNodeChildren( + node.id, + state.initialNodes, + state.initialEdges + ); + const childrenEdges = findEdgeChildren( + node.id, + childrenNodes, + state.initialEdges + ); + + const expand = () => { + if (isExpanded) { + return { + nodes: state.nodes.filter( + (sNode) => !childrenNodes.map((n) => n.id).includes(sNode.id) + ), + edges: state.edges.filter( + (sEdge) => !childrenEdges.map((n) => n.id).includes(sEdge.id) + ), + }; + } + + return { + nodes: state.nodes.concat(childrenNodes), + edges: state.edges.concat(childrenEdges), + }; + }; + + return { + ...state, + nodes: expand().nodes, + edges: expand().edges, + }; + }), +})); + +export default useGraph; diff --git a/src/utils/findEdgeChildren.ts b/src/utils/findEdgeChildren.ts index 9ce78f1..86bb8df 100644 --- a/src/utils/findEdgeChildren.ts +++ b/src/utils/findEdgeChildren.ts @@ -1,15 +1,17 @@ import { NodeData, EdgeData } from "reaflow/dist/types"; +export const findEdgeChildren = ( + selectedNode: string, + connections: NodeData[], + edges: EdgeData[] +) => { + const nodeIds = connections.map((n) => n.id); -export const findEdgeChildren = (selectedNode: string, connections: NodeData[], edges: EdgeData[]) => { + nodeIds.push(selectedNode); + const newEdges = edges.filter( + (e) => + nodeIds.includes(e.from as string) && nodeIds.includes(e.to as string) + ); - const nodeIds = connections.map((n) => n.id); - nodeIds.push(selectedNode); - const newEdges = edges.filter( - (e) => - nodeIds.includes(e.from as string) && nodeIds.includes(e.to as string) - ); - - return newEdges; - - }; \ No newline at end of file + return newEdges; +};