feat: improve nodes and edges state update
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
import { EdgeLabelRenderer, getStraightPath } from 'reactflow';
|
import { EdgeLabelRenderer, getStraightPath } from 'reactflow';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import { useMutation } from '@apollo/client';
|
|
||||||
import { CREATE_STEP } from 'graphql/mutations/create-step';
|
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { EdgesContext } from '../EditorNew';
|
||||||
|
|
||||||
export default function Edge({
|
export default function Edge({
|
||||||
sourceX,
|
sourceX,
|
||||||
@@ -12,11 +12,11 @@ export default function Edge({
|
|||||||
targetX,
|
targetX,
|
||||||
targetY,
|
targetY,
|
||||||
source,
|
source,
|
||||||
data: { flowId, setCurrentStepId, flowActive, layouted },
|
data: { layouted },
|
||||||
}) {
|
}) {
|
||||||
const [createStep, { loading: creationInProgress }] =
|
const { stepCreationInProgress, flowActive, onAddStep } =
|
||||||
useMutation(CREATE_STEP);
|
useContext(EdgesContext);
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const [edgePath, labelX, labelY] = getStraightPath({
|
const [edgePath, labelX, labelY] = getStraightPath({
|
||||||
sourceX,
|
sourceX,
|
||||||
sourceY,
|
sourceY,
|
||||||
@@ -24,30 +24,11 @@ export default function Edge({
|
|||||||
targetY,
|
targetY,
|
||||||
});
|
});
|
||||||
|
|
||||||
const addStep = async (previousStepId) => {
|
|
||||||
const mutationInput = {
|
|
||||||
previousStep: {
|
|
||||||
id: previousStepId,
|
|
||||||
},
|
|
||||||
flow: {
|
|
||||||
id: flowId,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const createdStep = await createStep({
|
|
||||||
variables: { input: mutationInput },
|
|
||||||
});
|
|
||||||
|
|
||||||
const createdStepId = createdStep.data.createStep.id;
|
|
||||||
setCurrentStepId(createdStepId);
|
|
||||||
await queryClient.invalidateQueries({ queryKey: ['flows', flowId] });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<EdgeLabelRenderer>
|
<EdgeLabelRenderer>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => addStep(source)}
|
onClick={() => onAddStep(source)}
|
||||||
color="primary"
|
color="primary"
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@@ -55,7 +36,7 @@ export default function Edge({
|
|||||||
pointerEvents: 'all',
|
pointerEvents: 'all',
|
||||||
visibility: layouted ? 'visible' : 'hidden',
|
visibility: layouted ? 'visible' : 'hidden',
|
||||||
}}
|
}}
|
||||||
disabled={creationInProgress || flowActive}
|
disabled={stepCreationInProgress || flowActive}
|
||||||
>
|
>
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -71,9 +52,6 @@ Edge.propTypes = {
|
|||||||
targetY: PropTypes.number.isRequired,
|
targetY: PropTypes.number.isRequired,
|
||||||
source: PropTypes.string.isRequired,
|
source: PropTypes.string.isRequired,
|
||||||
data: PropTypes.shape({
|
data: PropTypes.shape({
|
||||||
flowId: PropTypes.string.isRequired,
|
|
||||||
setCurrentStepId: PropTypes.func.isRequired,
|
|
||||||
flowActive: PropTypes.bool.isRequired,
|
|
||||||
layouted: PropTypes.bool,
|
layouted: PropTypes.bool,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
};
|
};
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
import { useEffect, useState, useCallback } from 'react';
|
import { useEffect, useCallback, createContext, useRef } from 'react';
|
||||||
import { useMutation } from '@apollo/client';
|
import { useMutation } from '@apollo/client';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { FlowPropType } from 'propTypes/propTypes';
|
import { FlowPropType } from 'propTypes/propTypes';
|
||||||
import ReactFlow, { useNodesState, useEdgesState, addEdge } from 'reactflow';
|
import ReactFlow, { useNodesState, useEdgesState } from 'reactflow';
|
||||||
import 'reactflow/dist/style.css';
|
import 'reactflow/dist/style.css';
|
||||||
import { UPDATE_STEP } from 'graphql/mutations/update-step';
|
import { UPDATE_STEP } from 'graphql/mutations/update-step';
|
||||||
|
import { CREATE_STEP } from 'graphql/mutations/create-step';
|
||||||
|
|
||||||
import { useAutoLayout } from './useAutoLayout';
|
import { useAutoLayout } from './useAutoLayout';
|
||||||
import { useScrollBoundries } from './useScrollBoundries';
|
import { useScrollBoundries } from './useScrollBoundries';
|
||||||
@@ -12,39 +13,69 @@ import FlowStepNode from './FlowStepNode/FlowStepNode';
|
|||||||
import Edge from './Edge/Edge';
|
import Edge from './Edge/Edge';
|
||||||
import InvisibleNode from './InvisibleNode/InvisibleNode';
|
import InvisibleNode from './InvisibleNode/InvisibleNode';
|
||||||
import { EditorWrapper } from './style';
|
import { EditorWrapper } from './style';
|
||||||
|
import {
|
||||||
|
generateEdgeId,
|
||||||
|
generateInitialEdges,
|
||||||
|
generateInitialNodes,
|
||||||
|
updatedCollapsedNodes,
|
||||||
|
} from './utils';
|
||||||
|
import { EDGE_TYPES, INVISIBLE_NODE_ID, NODE_TYPES } from './constants';
|
||||||
|
|
||||||
const nodeTypes = { flowStep: FlowStepNode, invisible: InvisibleNode };
|
export const EdgesContext = createContext();
|
||||||
|
export const NodesContext = createContext();
|
||||||
|
|
||||||
const edgeTypes = {
|
const nodeTypes = {
|
||||||
addNodeEdge: Edge,
|
[NODE_TYPES.FLOW_STEP]: FlowStepNode,
|
||||||
|
[NODE_TYPES.INVISIBLE]: InvisibleNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
const INVISIBLE_NODE_ID = 'invisible-node';
|
const edgeTypes = {
|
||||||
|
[EDGE_TYPES.ADD_NODE_EDGE]: Edge,
|
||||||
const generateEdgeId = (sourceId, targetId) => `${sourceId}-${targetId}`;
|
};
|
||||||
|
|
||||||
const EditorNew = ({ flow }) => {
|
const EditorNew = ({ flow }) => {
|
||||||
const [triggerStep] = flow.steps;
|
|
||||||
const [currentStepId, setCurrentStepId] = useState(triggerStep.id);
|
|
||||||
|
|
||||||
const [updateStep] = useMutation(UPDATE_STEP);
|
const [updateStep] = useMutation(UPDATE_STEP);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const [createStep, { loading: stepCreationInProgress }] =
|
||||||
|
useMutation(CREATE_STEP);
|
||||||
|
|
||||||
|
const [nodes, setNodes, onNodesChange] = useNodesState(
|
||||||
|
generateInitialNodes(flow),
|
||||||
|
);
|
||||||
|
const [edges, setEdges, onEdgesChange] = useEdgesState(
|
||||||
|
generateInitialEdges(flow),
|
||||||
|
);
|
||||||
|
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
|
||||||
useAutoLayout();
|
useAutoLayout();
|
||||||
useScrollBoundries();
|
useScrollBoundries();
|
||||||
|
|
||||||
const onConnect = useCallback(
|
const createdStepIdRef = useRef(null);
|
||||||
(params) => setEdges((eds) => addEdge(params, eds)),
|
|
||||||
[setEdges],
|
|
||||||
);
|
|
||||||
|
|
||||||
const openNextStep = useCallback(
|
const openNextStep = useCallback(
|
||||||
(nextStep) => () => {
|
(currentStepId) => {
|
||||||
setCurrentStepId(nextStep?.id);
|
setNodes((nodes) => {
|
||||||
|
const currentStepIndex = nodes.findIndex(
|
||||||
|
(node) => node.id === currentStepId,
|
||||||
|
);
|
||||||
|
if (currentStepIndex >= 0) {
|
||||||
|
const nextStep = nodes[currentStepIndex + 1];
|
||||||
|
return updatedCollapsedNodes(nodes, nextStep.id);
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[],
|
[setNodes],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onStepClose = useCallback(() => {
|
||||||
|
setNodes((nodes) => updatedCollapsedNodes(nodes));
|
||||||
|
}, [setNodes]);
|
||||||
|
|
||||||
|
const onStepOpen = useCallback(
|
||||||
|
(stepId) => {
|
||||||
|
setNodes((nodes) => updatedCollapsedNodes(nodes, stepId));
|
||||||
|
},
|
||||||
|
[setNodes],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onStepChange = useCallback(
|
const onStepChange = useCallback(
|
||||||
@@ -76,178 +107,166 @@ const EditorNew = ({ flow }) => {
|
|||||||
[flow.id, updateStep, queryClient],
|
[flow.id, updateStep, queryClient],
|
||||||
);
|
);
|
||||||
|
|
||||||
const generateEdges = useCallback((flow, prevEdges) => {
|
const onAddStep = useCallback(
|
||||||
const newEdges =
|
async (previousStepId) => {
|
||||||
flow.steps
|
const mutationInput = {
|
||||||
.map((step, i) => {
|
previousStep: {
|
||||||
const sourceId = step.id;
|
id: previousStepId,
|
||||||
const targetId = flow.steps[i + 1]?.id;
|
},
|
||||||
const edge = prevEdges?.find(
|
flow: {
|
||||||
(edge) => edge.id === generateEdgeId(sourceId, targetId),
|
id: flow.id,
|
||||||
);
|
},
|
||||||
if (targetId) {
|
};
|
||||||
return {
|
|
||||||
id: generateEdgeId(sourceId, targetId),
|
|
||||||
source: sourceId,
|
|
||||||
target: targetId,
|
|
||||||
type: 'addNodeEdge',
|
|
||||||
data: {
|
|
||||||
flowId: flow.id,
|
|
||||||
flowActive: flow.active,
|
|
||||||
setCurrentStepId,
|
|
||||||
layouted: !!edge,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter((edge) => !!edge) || [];
|
|
||||||
|
|
||||||
const lastStep = flow.steps[flow.steps.length - 1];
|
const {
|
||||||
|
data: { createStep: createdStep },
|
||||||
return lastStep
|
} = await createStep({
|
||||||
? [
|
variables: { input: mutationInput },
|
||||||
...newEdges,
|
|
||||||
{
|
|
||||||
id: generateEdgeId(lastStep.id, INVISIBLE_NODE_ID),
|
|
||||||
source: lastStep.id,
|
|
||||||
target: INVISIBLE_NODE_ID,
|
|
||||||
type: 'addNodeEdge',
|
|
||||||
data: {
|
|
||||||
flowId: flow.id,
|
|
||||||
flowActive: flow.active,
|
|
||||||
setCurrentStepId,
|
|
||||||
layouted: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: newEdges;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const generateNodes = useCallback(
|
|
||||||
(flow, prevNodes) => {
|
|
||||||
const newNodes = flow.steps.map((step, index) => {
|
|
||||||
const node = prevNodes?.find(({ id }) => id === step.id);
|
|
||||||
const collapsed = currentStepId !== step.id;
|
|
||||||
return {
|
|
||||||
id: step.id,
|
|
||||||
type: 'flowStep',
|
|
||||||
position: {
|
|
||||||
x: node ? node.position.x : 0,
|
|
||||||
y: node ? node.position.y : 0,
|
|
||||||
},
|
|
||||||
zIndex: collapsed ? 0 : 1,
|
|
||||||
data: {
|
|
||||||
step,
|
|
||||||
index: index,
|
|
||||||
flowId: flow.id,
|
|
||||||
collapsed,
|
|
||||||
openNextStep: openNextStep(flow.steps[index + 1]),
|
|
||||||
onOpen: () => setCurrentStepId(step.id),
|
|
||||||
onClose: () => setCurrentStepId(null),
|
|
||||||
onChange: onStepChange,
|
|
||||||
layouted: !!node,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const prevInvisibleNode = nodes.find((node) => node.type === 'invisible');
|
const createdStepId = createdStep.id;
|
||||||
|
await queryClient.invalidateQueries({ queryKey: ['flows', flow.id] });
|
||||||
return [
|
createdStepIdRef.current = createdStepId;
|
||||||
...newNodes,
|
|
||||||
{
|
|
||||||
id: INVISIBLE_NODE_ID,
|
|
||||||
type: 'invisible',
|
|
||||||
position: {
|
|
||||||
x: prevInvisibleNode ? prevInvisibleNode.position.x : 0,
|
|
||||||
y: prevInvisibleNode ? prevInvisibleNode.position.y : 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
[currentStepId, nodes, onStepChange, openNextStep],
|
[flow.id, createStep, queryClient],
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateNodesData = useCallback(
|
|
||||||
(steps) => {
|
|
||||||
setNodes((nodes) =>
|
|
||||||
nodes.map((node) => {
|
|
||||||
const step = steps.find((step) => step.id === node.id);
|
|
||||||
if (step) {
|
|
||||||
return { ...node, data: { ...node.data, step: { ...step } } };
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[setNodes],
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateEdgesData = useCallback(
|
|
||||||
(flow) => {
|
|
||||||
setEdges((edges) =>
|
|
||||||
edges.map((edge) => {
|
|
||||||
return {
|
|
||||||
...edge,
|
|
||||||
data: { ...edge.data, flowId: flow.id, flowActive: flow.active },
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[setEdges],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setNodes(
|
|
||||||
nodes.map((node) => {
|
|
||||||
if (node.type === 'flowStep') {
|
|
||||||
const collapsed = currentStepId !== node.data.step.id;
|
|
||||||
return {
|
|
||||||
...node,
|
|
||||||
zIndex: collapsed ? 0 : 1,
|
|
||||||
data: {
|
|
||||||
...node.data,
|
|
||||||
collapsed,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}, [currentStepId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (flow.steps.length + 1 !== nodes.length) {
|
if (flow.steps.length + 1 !== nodes.length) {
|
||||||
const newNodes = generateNodes(flow, nodes);
|
setNodes((nodes) => {
|
||||||
const newEdges = generateEdges(flow, edges);
|
const newNodes = flow.steps.map((step) => {
|
||||||
|
const createdStepId = createdStepIdRef.current;
|
||||||
|
const prevNode = nodes.find(({ id }) => id === step.id);
|
||||||
|
if (prevNode) {
|
||||||
|
return {
|
||||||
|
...prevNode,
|
||||||
|
zIndex: createdStepId ? 0 : prevNode.zIndex,
|
||||||
|
data: {
|
||||||
|
...prevNode.data,
|
||||||
|
collapsed: createdStepId ? true : prevNode.data.collapsed,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
id: step.id,
|
||||||
|
type: NODE_TYPES.FLOW_STEP,
|
||||||
|
position: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
zIndex: 1,
|
||||||
|
data: {
|
||||||
|
collapsed: false,
|
||||||
|
layouted: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
setNodes(newNodes);
|
const prevInvisible = nodes.find(({ id }) => id === INVISIBLE_NODE_ID);
|
||||||
setEdges(newEdges);
|
return [
|
||||||
} else {
|
...newNodes,
|
||||||
updateNodesData(flow.steps);
|
{
|
||||||
updateEdgesData(flow);
|
id: INVISIBLE_NODE_ID,
|
||||||
|
type: NODE_TYPES.INVISIBLE,
|
||||||
|
position: {
|
||||||
|
x: prevInvisible?.position.x || 0,
|
||||||
|
y: prevInvisible?.position.y || 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
setEdges((edges) => {
|
||||||
|
const newEdges = flow.steps
|
||||||
|
.map((step, i) => {
|
||||||
|
const sourceId = step.id;
|
||||||
|
const targetId = flow.steps[i + 1]?.id;
|
||||||
|
const edge = edges?.find(
|
||||||
|
(edge) => edge.id === generateEdgeId(sourceId, targetId),
|
||||||
|
);
|
||||||
|
if (targetId) {
|
||||||
|
return {
|
||||||
|
id: generateEdgeId(sourceId, targetId),
|
||||||
|
source: sourceId,
|
||||||
|
target: targetId,
|
||||||
|
type: 'addNodeEdge',
|
||||||
|
data: {
|
||||||
|
layouted: edge ? edge?.data.layouted : false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter((edge) => !!edge);
|
||||||
|
|
||||||
|
const lastStep = flow.steps[flow.steps.length - 1];
|
||||||
|
const lastEdge = edges[edges.length - 1];
|
||||||
|
|
||||||
|
return lastStep
|
||||||
|
? [
|
||||||
|
...newEdges,
|
||||||
|
{
|
||||||
|
id: generateEdgeId(lastStep.id, INVISIBLE_NODE_ID),
|
||||||
|
source: lastStep.id,
|
||||||
|
target: INVISIBLE_NODE_ID,
|
||||||
|
type: 'addNodeEdge',
|
||||||
|
data: {
|
||||||
|
layouted:
|
||||||
|
lastEdge?.id ===
|
||||||
|
generateEdgeId(lastStep.id, INVISIBLE_NODE_ID)
|
||||||
|
? lastEdge?.data.layouted
|
||||||
|
: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: newEdges;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (createdStepIdRef.current) {
|
||||||
|
createdStepIdRef.current = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [flow]);
|
}, [flow.steps]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditorWrapper direction="column">
|
<NodesContext.Provider
|
||||||
<ReactFlow
|
value={{
|
||||||
nodes={nodes}
|
openNextStep,
|
||||||
edges={edges}
|
onStepOpen,
|
||||||
onNodesChange={onNodesChange}
|
onStepClose,
|
||||||
onEdgesChange={onEdgesChange}
|
onStepChange,
|
||||||
onConnect={onConnect}
|
flowId: flow.id,
|
||||||
nodeTypes={nodeTypes}
|
steps: flow.steps,
|
||||||
edgeTypes={edgeTypes}
|
}}
|
||||||
panOnScroll
|
>
|
||||||
panOnScrollMode="vertical"
|
<EdgesContext.Provider
|
||||||
panOnDrag={false}
|
value={{
|
||||||
zoomOnScroll={false}
|
stepCreationInProgress,
|
||||||
zoomOnPinch={false}
|
onAddStep,
|
||||||
zoomOnDoubleClick={false}
|
flowActive: flow.active,
|
||||||
panActivationKeyCode={null}
|
}}
|
||||||
proOptions={{ hideAttribution: true }}
|
>
|
||||||
/>
|
<EditorWrapper direction="column">
|
||||||
</EditorWrapper>
|
<ReactFlow
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
onNodesChange={onNodesChange}
|
||||||
|
onEdgesChange={onEdgesChange}
|
||||||
|
nodeTypes={nodeTypes}
|
||||||
|
edgeTypes={edgeTypes}
|
||||||
|
panOnScroll
|
||||||
|
panOnScrollMode="vertical"
|
||||||
|
panOnDrag={false}
|
||||||
|
zoomOnScroll={false}
|
||||||
|
zoomOnPinch={false}
|
||||||
|
zoomOnDoubleClick={false}
|
||||||
|
panActivationKeyCode={null}
|
||||||
|
proOptions={{ hideAttribution: true }}
|
||||||
|
/>
|
||||||
|
</EditorWrapper>
|
||||||
|
</EdgesContext.Provider>
|
||||||
|
</NodesContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,25 +1,24 @@
|
|||||||
import { Handle, Position } from 'reactflow';
|
import { Handle, Position } from 'reactflow';
|
||||||
import { Box } from '@mui/material';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import FlowStep from 'components/FlowStep';
|
import FlowStep from 'components/FlowStep';
|
||||||
import { StepPropType } from 'propTypes/propTypes';
|
|
||||||
|
|
||||||
import { NodeWrapper, NodeInnerWrapper } from './style.js';
|
import { NodeWrapper, NodeInnerWrapper } from './style.js';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { NodesContext } from '../EditorNew.jsx';
|
||||||
|
|
||||||
function FlowStepNode({
|
function FlowStepNode({ data: { collapsed, layouted }, id }) {
|
||||||
data: {
|
const {
|
||||||
step,
|
|
||||||
index,
|
|
||||||
flowId,
|
|
||||||
collapsed,
|
|
||||||
openNextStep,
|
openNextStep,
|
||||||
onOpen,
|
onStepOpen,
|
||||||
onClose,
|
onStepClose,
|
||||||
onChange,
|
onStepChange,
|
||||||
layouted,
|
flowId,
|
||||||
},
|
steps,
|
||||||
}) {
|
} = useContext(NodesContext);
|
||||||
|
|
||||||
|
const step = steps.find(({ id: stepId }) => stepId === id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeWrapper
|
<NodeWrapper
|
||||||
className="nodrag"
|
className="nodrag"
|
||||||
@@ -34,16 +33,17 @@ function FlowStepNode({
|
|||||||
isConnectable={false}
|
isConnectable={false}
|
||||||
style={{ visibility: 'hidden' }}
|
style={{ visibility: 'hidden' }}
|
||||||
/>
|
/>
|
||||||
<FlowStep
|
{step && (
|
||||||
step={step}
|
<FlowStep
|
||||||
index={index + 1}
|
step={step}
|
||||||
collapsed={collapsed}
|
collapsed={collapsed}
|
||||||
onOpen={onOpen}
|
onOpen={() => onStepOpen(step.id)}
|
||||||
onClose={onClose}
|
onClose={onStepClose}
|
||||||
onChange={onChange}
|
onChange={onStepChange}
|
||||||
flowId={flowId}
|
flowId={flowId}
|
||||||
onContinue={openNextStep}
|
onContinue={() => openNextStep(step.id)}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
<Handle
|
<Handle
|
||||||
type="source"
|
type="source"
|
||||||
position={Position.Bottom}
|
position={Position.Bottom}
|
||||||
@@ -56,15 +56,9 @@ function FlowStepNode({
|
|||||||
}
|
}
|
||||||
|
|
||||||
FlowStepNode.propTypes = {
|
FlowStepNode.propTypes = {
|
||||||
|
id: PropTypes.string,
|
||||||
data: PropTypes.shape({
|
data: PropTypes.shape({
|
||||||
step: StepPropType.isRequired,
|
|
||||||
index: PropTypes.number.isRequired,
|
|
||||||
flowId: PropTypes.string.isRequired,
|
|
||||||
collapsed: PropTypes.bool.isRequired,
|
collapsed: PropTypes.bool.isRequired,
|
||||||
openNextStep: PropTypes.func.isRequired,
|
|
||||||
onOpen: PropTypes.func.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
layouted: PropTypes.bool.isRequired,
|
layouted: PropTypes.bool.isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
};
|
};
|
||||||
|
10
packages/web/src/components/EditorNew/constants.js
Normal file
10
packages/web/src/components/EditorNew/constants.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export const INVISIBLE_NODE_ID = 'invisible-node';
|
||||||
|
|
||||||
|
export const NODE_TYPES = {
|
||||||
|
FLOW_STEP: 'flowStep',
|
||||||
|
INVISIBLE: 'invisible',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EDGE_TYPES = {
|
||||||
|
ADD_NODE_EDGE: 'addNodeEdge',
|
||||||
|
};
|
88
packages/web/src/components/EditorNew/utils.js
Normal file
88
packages/web/src/components/EditorNew/utils.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { INVISIBLE_NODE_ID, NODE_TYPES } from './constants';
|
||||||
|
|
||||||
|
export const generateEdgeId = (sourceId, targetId) => `${sourceId}-${targetId}`;
|
||||||
|
|
||||||
|
export const updatedCollapsedNodes = (nodes, openStepId) => {
|
||||||
|
return nodes.map((node) => {
|
||||||
|
if (node.type !== NODE_TYPES.FLOW_STEP) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
const collapsed = node.id !== openStepId;
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
zIndex: collapsed ? 0 : 1,
|
||||||
|
data: { ...node.data, collapsed },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateInitialNodes = (flow) => {
|
||||||
|
const newNodes = flow.steps.map((step, index) => {
|
||||||
|
const collapsed = index !== 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: step.id,
|
||||||
|
type: NODE_TYPES.FLOW_STEP,
|
||||||
|
position: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
zIndex: collapsed ? 0 : 1,
|
||||||
|
data: {
|
||||||
|
collapsed,
|
||||||
|
layouted: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return [
|
||||||
|
...newNodes,
|
||||||
|
{
|
||||||
|
id: INVISIBLE_NODE_ID,
|
||||||
|
type: NODE_TYPES.INVISIBLE,
|
||||||
|
position: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateInitialEdges = (flow) => {
|
||||||
|
const newEdges = flow.steps
|
||||||
|
.map((step, i) => {
|
||||||
|
const sourceId = step.id;
|
||||||
|
const targetId = flow.steps[i + 1]?.id;
|
||||||
|
if (targetId) {
|
||||||
|
return {
|
||||||
|
id: generateEdgeId(sourceId, targetId),
|
||||||
|
source: sourceId,
|
||||||
|
target: targetId,
|
||||||
|
type: 'addNodeEdge',
|
||||||
|
data: {
|
||||||
|
layouted: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter((edge) => !!edge);
|
||||||
|
|
||||||
|
const lastStep = flow.steps[flow.steps.length - 1];
|
||||||
|
|
||||||
|
return lastStep
|
||||||
|
? [
|
||||||
|
...newEdges,
|
||||||
|
{
|
||||||
|
id: generateEdgeId(lastStep.id, INVISIBLE_NODE_ID),
|
||||||
|
source: lastStep.id,
|
||||||
|
target: INVISIBLE_NODE_ID,
|
||||||
|
type: 'addNodeEdge',
|
||||||
|
data: {
|
||||||
|
layouted: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: newEdges;
|
||||||
|
};
|
@@ -360,7 +360,6 @@ function FlowStep(props) {
|
|||||||
FlowStep.propTypes = {
|
FlowStep.propTypes = {
|
||||||
collapsed: PropTypes.bool,
|
collapsed: PropTypes.bool,
|
||||||
step: StepPropType.isRequired,
|
step: StepPropType.isRequired,
|
||||||
index: PropTypes.number,
|
|
||||||
onOpen: PropTypes.func,
|
onOpen: PropTypes.func,
|
||||||
onClose: PropTypes.func,
|
onClose: PropTypes.func,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
|
Reference in New Issue
Block a user