Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
96c3c19a50 |
@@ -1,57 +0,0 @@
|
||||
import { EdgeLabelRenderer, getStraightPath } from 'reactflow';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import { useContext } from 'react';
|
||||
import { EdgesContext } from '../EditorNew';
|
||||
|
||||
export default function Edge({
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
source,
|
||||
data: { laidOut },
|
||||
}) {
|
||||
const { stepCreationInProgress, flowActive, onAddStep } =
|
||||
useContext(EdgesContext);
|
||||
|
||||
const [edgePath, labelX, labelY] = getStraightPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<EdgeLabelRenderer>
|
||||
<IconButton
|
||||
onClick={() => onAddStep(source)}
|
||||
color="primary"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
||||
pointerEvents: 'all',
|
||||
visibility: laidOut ? 'visible' : 'hidden',
|
||||
}}
|
||||
disabled={stepCreationInProgress || flowActive}
|
||||
>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
</EdgeLabelRenderer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Edge.propTypes = {
|
||||
sourceX: PropTypes.number.isRequired,
|
||||
sourceY: PropTypes.number.isRequired,
|
||||
targetX: PropTypes.number.isRequired,
|
||||
targetY: PropTypes.number.isRequired,
|
||||
source: PropTypes.string.isRequired,
|
||||
data: PropTypes.shape({
|
||||
laidOut: PropTypes.bool,
|
||||
}).isRequired,
|
||||
};
|
@@ -0,0 +1,69 @@
|
||||
import { EdgeLabelRenderer, getStraightPath, BaseEdge } from 'reactflow';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import { useContext } from 'react';
|
||||
import { EdgesContext } from '../../EditorNew';
|
||||
import { Tooltip } from '@mui/material';
|
||||
|
||||
export default function NodeEdge({
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
source,
|
||||
data: { laidOut },
|
||||
}) {
|
||||
const { stepCreationInProgress, flowActive, onAddStep } =
|
||||
useContext(EdgesContext);
|
||||
|
||||
const [edgePath, labelX, labelY] = getStraightPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
});
|
||||
|
||||
const handleAddStep = () => {
|
||||
onAddStep(source);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseEdge path={edgePath} />
|
||||
<EdgeLabelRenderer>
|
||||
<Tooltip title="Add step">
|
||||
<IconButton
|
||||
onClick={handleAddStep}
|
||||
color="primary"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
||||
pointerEvents: 'all',
|
||||
backgroundColor: '#fafafa',
|
||||
'&:hover': {
|
||||
backgroundColor: '#f0f3fa',
|
||||
},
|
||||
// visibility: laidOut ? 'visible' : 'hidden',
|
||||
}}
|
||||
disabled={stepCreationInProgress || flowActive}
|
||||
>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</EdgeLabelRenderer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
NodeEdge.propTypes = {
|
||||
sourceX: PropTypes.number.isRequired,
|
||||
sourceY: PropTypes.number.isRequired,
|
||||
targetX: PropTypes.number.isRequired,
|
||||
targetY: PropTypes.number.isRequired,
|
||||
source: PropTypes.string.isRequired,
|
||||
data: PropTypes.shape({
|
||||
laidOut: PropTypes.bool,
|
||||
}).isRequired,
|
||||
};
|
@@ -0,0 +1,95 @@
|
||||
import { EdgeLabelRenderer, getStraightPath, BaseEdge } from 'reactflow';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import { useContext, useState } from 'react';
|
||||
import { EdgesContext } from '../../EditorNew';
|
||||
import { Tooltip } from '@mui/material';
|
||||
|
||||
export default function NodeOrPathsEdge({
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
source,
|
||||
data: { laidOut },
|
||||
}) {
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const handleClick = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const { stepCreationInProgress, flowActive, onAddStep, onAddPaths } =
|
||||
useContext(EdgesContext);
|
||||
|
||||
const [edgePath, labelX, labelY] = getStraightPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
});
|
||||
|
||||
const handleAddStep = () => {
|
||||
onAddStep(source);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handleAddPaths = () => {
|
||||
onAddPaths(source);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseEdge path={edgePath} />
|
||||
<EdgeLabelRenderer>
|
||||
<Tooltip title="Add step or paths">
|
||||
<IconButton
|
||||
onClick={handleClick}
|
||||
color="primary"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
||||
pointerEvents: 'all',
|
||||
backgroundColor: '#fafafa',
|
||||
'&:hover': {
|
||||
backgroundColor: '#f0f3fa',
|
||||
},
|
||||
// visibility: laidOut ? 'visible' : 'hidden',
|
||||
}}
|
||||
disabled={stepCreationInProgress || flowActive}
|
||||
>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
anchorOrigin={{ horizontal: 'right', vertical: 'top' }}
|
||||
>
|
||||
<MenuItem onClick={handleAddStep}>Step</MenuItem>
|
||||
<MenuItem onClick={handleAddPaths}>Paths</MenuItem>
|
||||
</Menu>
|
||||
</EdgeLabelRenderer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
NodeOrPathsEdge.propTypes = {
|
||||
sourceX: PropTypes.number.isRequired,
|
||||
sourceY: PropTypes.number.isRequired,
|
||||
targetX: PropTypes.number.isRequired,
|
||||
targetY: PropTypes.number.isRequired,
|
||||
source: PropTypes.string.isRequired,
|
||||
data: PropTypes.shape({
|
||||
laidOut: PropTypes.bool,
|
||||
}).isRequired,
|
||||
};
|
@@ -0,0 +1,69 @@
|
||||
import { EdgeLabelRenderer, getStraightPath, BaseEdge } from 'reactflow';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import { useContext } from 'react';
|
||||
import { EdgesContext } from '../../EditorNew';
|
||||
import { Tooltip } from '@mui/material';
|
||||
|
||||
export default function PathsEdge({
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
source,
|
||||
data: { laidOut },
|
||||
}) {
|
||||
const { stepCreationInProgress, flowActive, onAddPath } =
|
||||
useContext(EdgesContext);
|
||||
|
||||
const [edgePath, labelX, labelY] = getStraightPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
});
|
||||
|
||||
const handleAddPath = () => {
|
||||
onAddPath(source);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseEdge path={edgePath} />
|
||||
<EdgeLabelRenderer>
|
||||
<Tooltip title="Add path">
|
||||
<IconButton
|
||||
onClick={handleAddPath}
|
||||
color="primary"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
||||
pointerEvents: 'all',
|
||||
backgroundColor: '#fafafa',
|
||||
'&:hover': {
|
||||
backgroundColor: '#f0f3fa',
|
||||
},
|
||||
// visibility: laidOut ? 'visible' : 'hidden',
|
||||
}}
|
||||
disabled={stepCreationInProgress || flowActive}
|
||||
>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</EdgeLabelRenderer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
PathsEdge.propTypes = {
|
||||
sourceX: PropTypes.number.isRequired,
|
||||
sourceY: PropTypes.number.isRequired,
|
||||
targetX: PropTypes.number.isRequired,
|
||||
targetY: PropTypes.number.isRequired,
|
||||
source: PropTypes.string.isRequired,
|
||||
data: PropTypes.shape({
|
||||
laidOut: PropTypes.bool,
|
||||
}).isRequired,
|
||||
};
|
@@ -1,25 +1,24 @@
|
||||
import { useEffect, useCallback, createContext, useRef } from 'react';
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
// import { useMutation } from '@apollo/client';
|
||||
// import { useQueryClient } from '@tanstack/react-query';
|
||||
import { FlowPropType } from 'propTypes/propTypes';
|
||||
import ReactFlow, { useNodesState, useEdgesState } from 'reactflow';
|
||||
import 'reactflow/dist/style.css';
|
||||
import { UPDATE_STEP } from 'graphql/mutations/update-step';
|
||||
import { CREATE_STEP } from 'graphql/mutations/create-step';
|
||||
// import { UPDATE_STEP } from 'graphql/mutations/update-step';
|
||||
// import { CREATE_STEP } from 'graphql/mutations/create-step';
|
||||
|
||||
import { useAutoLayout } from './useAutoLayout';
|
||||
import { useScrollBoundaries } from './useScrollBoundaries';
|
||||
import FlowStepNode from './FlowStepNode/FlowStepNode';
|
||||
import Edge from './Edge/Edge';
|
||||
import InvisibleNode from './InvisibleNode/InvisibleNode';
|
||||
import NodeOrPathsEdge from './Edges/NodeOrPathsEdge/NodeOrPathsEdge';
|
||||
import FlowStepNode from './Nodes/FlowStepNode/FlowStepNode';
|
||||
import InvisibleNode from './Nodes/InvisibleNode/InvisibleNode';
|
||||
import PathsNode from './Nodes/PathsNode/PathsNode';
|
||||
import { EditorWrapper } from './style';
|
||||
import {
|
||||
generateEdgeId,
|
||||
generateInitialEdges,
|
||||
generateInitialNodes,
|
||||
updatedCollapsedNodes,
|
||||
} from './utils';
|
||||
import { EDGE_TYPES, INVISIBLE_NODE_ID, NODE_TYPES } from './constants';
|
||||
import { generateEdges, generateNodes, updatedCollapsedNodes } from './utils';
|
||||
import { EDGE_TYPES, NODE_TYPES } from './constants';
|
||||
import { useFlow } from './temp/useFlow';
|
||||
import PathNode from './Nodes/PathNode/PathNode';
|
||||
import PathsEdge from './Edges/PathsEdge/PathsEdge';
|
||||
import NodeEdge from './Edges/NodeEdge/NodeEdge';
|
||||
|
||||
export const EdgesContext = createContext();
|
||||
export const NodesContext = createContext();
|
||||
@@ -27,27 +26,34 @@ export const NodesContext = createContext();
|
||||
const nodeTypes = {
|
||||
[NODE_TYPES.FLOW_STEP]: FlowStepNode,
|
||||
[NODE_TYPES.INVISIBLE]: InvisibleNode,
|
||||
[NODE_TYPES.PATHS]: PathsNode,
|
||||
[NODE_TYPES.PATH]: PathNode,
|
||||
};
|
||||
|
||||
const edgeTypes = {
|
||||
[EDGE_TYPES.ADD_NODE_EDGE]: Edge,
|
||||
[EDGE_TYPES.ADD_NODE_OR_PATHS_EDGE]: NodeOrPathsEdge,
|
||||
[EDGE_TYPES.ADD_PATH_EDGE]: PathsEdge,
|
||||
[EDGE_TYPES.ADD_NODE_EDGE]: NodeEdge,
|
||||
};
|
||||
|
||||
const EditorNew = ({ flow }) => {
|
||||
const [updateStep] = useMutation(UPDATE_STEP);
|
||||
const queryClient = useQueryClient();
|
||||
const [createStep, { loading: stepCreationInProgress }] =
|
||||
useMutation(CREATE_STEP);
|
||||
const EditorNew = () =>
|
||||
// { flow }
|
||||
{
|
||||
const { flow, createStep, createPaths, createPath } = useFlow();
|
||||
// const [updateStep] = useMutation(UPDATE_STEP);
|
||||
// const queryClient = useQueryClient();
|
||||
// const [createStep, { loading: stepCreationInProgress }] =
|
||||
// useMutation(CREATE_STEP);
|
||||
const stepCreationInProgress = false;
|
||||
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(
|
||||
generateInitialNodes(flow),
|
||||
generateNodes({ steps: flow.steps }),
|
||||
);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(
|
||||
generateInitialEdges(flow),
|
||||
generateEdges({ steps: flow.steps }),
|
||||
);
|
||||
|
||||
useAutoLayout();
|
||||
useScrollBoundaries();
|
||||
|
||||
const createdStepIdRef = useRef(null);
|
||||
|
||||
@@ -80,153 +86,56 @@ const EditorNew = ({ flow }) => {
|
||||
|
||||
const onStepChange = useCallback(
|
||||
async (step) => {
|
||||
const mutationInput = {
|
||||
id: step.id,
|
||||
key: step.key,
|
||||
parameters: step.parameters,
|
||||
connection: {
|
||||
id: step.connection?.id,
|
||||
// const mutationInput = {
|
||||
// id: step.id,
|
||||
// key: step.key,
|
||||
// parameters: step.parameters,
|
||||
// connection: {
|
||||
// id: step.connection?.id,
|
||||
// },
|
||||
// flow: {
|
||||
// id: flow.id,
|
||||
// },
|
||||
// };
|
||||
// if (step.appKey) {
|
||||
// mutationInput.appKey = step.appKey;
|
||||
// }
|
||||
// const updated = await updateStep({
|
||||
// variables: { input: mutationInput },
|
||||
// });
|
||||
// await queryClient.invalidateQueries({
|
||||
// queryKey: ['steps', step.id, 'connection'],
|
||||
// });
|
||||
// await queryClient.invalidateQueries({ queryKey: ['flows', flow.id] });
|
||||
},
|
||||
flow: {
|
||||
id: flow.id,
|
||||
},
|
||||
};
|
||||
|
||||
if (step.appKey) {
|
||||
mutationInput.appKey = step.appKey;
|
||||
}
|
||||
|
||||
await updateStep({
|
||||
variables: { input: mutationInput },
|
||||
});
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ['steps', step.id, 'connection'],
|
||||
});
|
||||
await queryClient.invalidateQueries({ queryKey: ['flows', flow.id] });
|
||||
},
|
||||
[flow.id, updateStep, queryClient],
|
||||
// [flow.id, updateStep, queryClient],
|
||||
);
|
||||
|
||||
const onAddStep = useCallback(
|
||||
async (previousStepId) => {
|
||||
const mutationInput = {
|
||||
previousStep: {
|
||||
id: previousStepId,
|
||||
},
|
||||
flow: {
|
||||
id: flow.id,
|
||||
},
|
||||
};
|
||||
|
||||
const {
|
||||
data: { createStep: createdStep },
|
||||
} = await createStep({
|
||||
variables: { input: mutationInput },
|
||||
});
|
||||
|
||||
const createdStepId = createdStep.id;
|
||||
await queryClient.invalidateQueries({ queryKey: ['flows', flow.id] });
|
||||
const onAddStep = async (previousStepId) => {
|
||||
const createdStepId = createStep(flow, previousStepId);
|
||||
createdStepIdRef.current = createdStepId;
|
||||
},
|
||||
[flow.id, createStep, queryClient],
|
||||
);
|
||||
};
|
||||
|
||||
console.log({ flow });
|
||||
|
||||
useEffect(() => {
|
||||
if (flow.steps.length + 1 !== nodes.length) {
|
||||
setNodes((nodes) => {
|
||||
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,
|
||||
laidOut: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const prevInvisible = nodes.find(({ id }) => id === INVISIBLE_NODE_ID);
|
||||
return [
|
||||
...newNodes,
|
||||
{
|
||||
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 (flow.steps.length + 1 !== nodes.length) {
|
||||
setNodes((nodes) =>
|
||||
generateNodes({
|
||||
prevNodes: nodes,
|
||||
steps: flow.steps,
|
||||
createdStepId: createdStepIdRef.current,
|
||||
}),
|
||||
);
|
||||
if (targetId) {
|
||||
return {
|
||||
id: generateEdgeId(sourceId, targetId),
|
||||
source: sourceId,
|
||||
target: targetId,
|
||||
type: 'addNodeEdge',
|
||||
data: {
|
||||
laidOut: edge ? edge?.data.laidOut : 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: {
|
||||
laidOut:
|
||||
lastEdge?.id ===
|
||||
generateEdgeId(lastStep.id, INVISIBLE_NODE_ID)
|
||||
? lastEdge?.data.laidOut
|
||||
: false,
|
||||
},
|
||||
},
|
||||
]
|
||||
: newEdges;
|
||||
});
|
||||
setEdges((edges) =>
|
||||
generateEdges({ prevEdges: edges, steps: flow.steps }),
|
||||
);
|
||||
|
||||
if (createdStepIdRef.current) {
|
||||
createdStepIdRef.current = null;
|
||||
}
|
||||
}
|
||||
// }
|
||||
}, [flow.steps]);
|
||||
|
||||
return (
|
||||
@@ -244,6 +153,8 @@ const EditorNew = ({ flow }) => {
|
||||
value={{
|
||||
stepCreationInProgress,
|
||||
onAddStep,
|
||||
onAddPaths: createPaths,
|
||||
onAddPath: createPath,
|
||||
flowActive: flow.active,
|
||||
}}
|
||||
>
|
||||
@@ -255,13 +166,9 @@ const EditorNew = ({ flow }) => {
|
||||
onEdgesChange={onEdgesChange}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
panOnScroll
|
||||
panOnScrollMode="vertical"
|
||||
panOnDrag={false}
|
||||
zoomOnScroll={false}
|
||||
zoomOnPinch={false}
|
||||
zoomOnDoubleClick={false}
|
||||
panActivationKeyCode={null}
|
||||
fitView
|
||||
maxZoom={1}
|
||||
minZoom={0.001}
|
||||
proOptions={{ hideAttribution: true }}
|
||||
/>
|
||||
</EditorWrapper>
|
||||
|
@@ -1,60 +0,0 @@
|
||||
import { Handle, Position } from 'reactflow';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import FlowStep from 'components/FlowStep';
|
||||
|
||||
import { NodeWrapper, NodeInnerWrapper } from './style.js';
|
||||
import { useContext } from 'react';
|
||||
import { NodesContext } from '../EditorNew.jsx';
|
||||
|
||||
function FlowStepNode({ data: { collapsed, laidOut }, id }) {
|
||||
const { openNextStep, onStepOpen, onStepClose, onStepChange, flowId, steps } =
|
||||
useContext(NodesContext);
|
||||
|
||||
const step = steps.find(({ id: stepId }) => stepId === id);
|
||||
|
||||
return (
|
||||
<NodeWrapper
|
||||
className="nodrag"
|
||||
sx={{
|
||||
visibility: laidOut ? 'visible' : 'hidden',
|
||||
}}
|
||||
>
|
||||
<NodeInnerWrapper>
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Top}
|
||||
isConnectable={false}
|
||||
style={{ visibility: 'hidden' }}
|
||||
/>
|
||||
{step && (
|
||||
<FlowStep
|
||||
step={step}
|
||||
collapsed={collapsed}
|
||||
onOpen={() => onStepOpen(step.id)}
|
||||
onClose={onStepClose}
|
||||
onChange={onStepChange}
|
||||
flowId={flowId}
|
||||
onContinue={() => openNextStep(step.id)}
|
||||
/>
|
||||
)}
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Bottom}
|
||||
isConnectable={false}
|
||||
style={{ visibility: 'hidden' }}
|
||||
/>
|
||||
</NodeInnerWrapper>
|
||||
</NodeWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
FlowStepNode.propTypes = {
|
||||
id: PropTypes.string,
|
||||
data: PropTypes.shape({
|
||||
collapsed: PropTypes.bool.isRequired,
|
||||
laidOut: PropTypes.bool.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default FlowStepNode;
|
@@ -0,0 +1,69 @@
|
||||
import { Handle, Position } from 'reactflow';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import FlowStep from 'components/FlowStep';
|
||||
|
||||
import { NodeWrapper, NodeInnerWrapper } from './style.js';
|
||||
import { useContext } from 'react';
|
||||
import { NodesContext } from '../../EditorNew.jsx';
|
||||
import { findStepByStepId } from 'components/EditorNew/utils.js';
|
||||
|
||||
function FlowStepNode({ data: { collapsed, laidOut }, id }) {
|
||||
const { openNextStep, onStepOpen, onStepClose, onStepChange, flowId, steps } =
|
||||
useContext(NodesContext);
|
||||
|
||||
const step = findStepByStepId({ steps }, id);
|
||||
|
||||
return (
|
||||
// <NodeWrapper
|
||||
// sx={{
|
||||
// visibility: laidOut ? 'visible' : 'hidden',
|
||||
// }}
|
||||
// >
|
||||
<NodeInnerWrapper
|
||||
sx={
|
||||
{
|
||||
// visibility: laidOut ? 'visible' : 'hidden',
|
||||
}
|
||||
}
|
||||
id="flowStepId"
|
||||
className="nodrag"
|
||||
>
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Top}
|
||||
isConnectable={false}
|
||||
style={{ visibility: 'hidden' }}
|
||||
/>
|
||||
{step && (
|
||||
<FlowStep
|
||||
step={step}
|
||||
collapsed={collapsed}
|
||||
onOpen={() => onStepOpen(step.id)}
|
||||
onClose={onStepClose}
|
||||
onChange={onStepChange}
|
||||
flowId={flowId}
|
||||
onContinue={() => openNextStep(step.id)}
|
||||
collapseAnimation={false}
|
||||
/>
|
||||
)}
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Bottom}
|
||||
isConnectable={false}
|
||||
style={{ visibility: 'hidden' }}
|
||||
/>
|
||||
</NodeInnerWrapper>
|
||||
// </NodeWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
FlowStepNode.propTypes = {
|
||||
id: PropTypes.string,
|
||||
data: PropTypes.shape({
|
||||
collapsed: PropTypes.bool.isRequired,
|
||||
laidOut: PropTypes.bool.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default FlowStepNode;
|
@@ -9,6 +9,6 @@ export const NodeWrapper = styled(Box)(({ theme }) => ({
|
||||
}));
|
||||
|
||||
export const NodeInnerWrapper = styled(Box)(({ theme }) => ({
|
||||
maxWidth: 900,
|
||||
width: 900,
|
||||
flex: 1,
|
||||
}));
|
@@ -0,0 +1,98 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
import { Box, Stack, Typography } from '@mui/material';
|
||||
import { useRef, useState } from 'react';
|
||||
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import { Wrapper } from './style';
|
||||
|
||||
/* TODO
|
||||
- add delete
|
||||
- add rename
|
||||
- add translations
|
||||
- add collapsing?
|
||||
*/
|
||||
|
||||
function PathNode({ data: { laidOut } }) {
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const contextButtonRef = useRef(null);
|
||||
|
||||
const onContextMenuClose = (event) => {
|
||||
event.stopPropagation();
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const onContextMenuClick = (event) => {
|
||||
event.stopPropagation();
|
||||
setAnchorEl(contextButtonRef.current);
|
||||
};
|
||||
|
||||
const deletePath = () => {
|
||||
setAnchorEl(null);
|
||||
onContextMenuClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
className="nodrag"
|
||||
sx={
|
||||
{
|
||||
// visibility: laidOut ? 'visible' : 'hidden',
|
||||
}
|
||||
}
|
||||
>
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Top}
|
||||
isConnectable={false}
|
||||
style={{ visibility: 'hidden' }}
|
||||
/>
|
||||
|
||||
<Wrapper>
|
||||
<Stack
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
direction="row"
|
||||
>
|
||||
<Typography sx={{ pr: 2 }}>Path</Typography>
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={onContextMenuClick}
|
||||
ref={contextButtonRef}
|
||||
>
|
||||
<MoreHorizIcon />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Wrapper>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Bottom}
|
||||
isConnectable={false}
|
||||
style={{ visibility: 'hidden' }}
|
||||
/>
|
||||
</Box>
|
||||
{anchorEl && (
|
||||
<Menu
|
||||
open={true}
|
||||
onClose={onContextMenuClose}
|
||||
hideBackdrop={false}
|
||||
anchorEl={anchorEl}
|
||||
>
|
||||
<MenuItem onClick={deletePath}>Delete</MenuItem>
|
||||
</Menu>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
PathNode.propTypes = {
|
||||
data: PropTypes.shape({
|
||||
laidOut: PropTypes.bool,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default PathNode;
|
@@ -0,0 +1,8 @@
|
||||
import { styled } from '@mui/material/styles';
|
||||
import Box from '@mui/material/Box';
|
||||
|
||||
export const Wrapper = styled(Box)(({ theme }) => ({
|
||||
padding: theme.spacing(1, 2),
|
||||
backgroundColor: '#0059f714',
|
||||
borderRadius: 20,
|
||||
}));
|
@@ -0,0 +1,108 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
import { Avatar, Box, Stack, Typography } from '@mui/material';
|
||||
import { useRef, useState } from 'react';
|
||||
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import CallSplitIcon from '@mui/icons-material/CallSplit';
|
||||
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import { Wrapper } from './style';
|
||||
|
||||
/* TODO
|
||||
- add delete
|
||||
- add rename
|
||||
- add translations
|
||||
- add collapsing?
|
||||
*/
|
||||
|
||||
function PathsNode({ data: { laidOut } }) {
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const contextButtonRef = useRef(null);
|
||||
|
||||
const onContextMenuClose = (event) => {
|
||||
event.stopPropagation();
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const onContextMenuClick = (event) => {
|
||||
event.stopPropagation();
|
||||
setAnchorEl(contextButtonRef.current);
|
||||
};
|
||||
|
||||
const deletePaths = () => {
|
||||
setAnchorEl(null);
|
||||
onContextMenuClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
width={900}
|
||||
className="nodrag"
|
||||
sx={
|
||||
{
|
||||
// visibility: laidOut ? 'visible' : 'hidden',
|
||||
}
|
||||
}
|
||||
>
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Top}
|
||||
isConnectable={false}
|
||||
style={{ visibility: 'hidden' }}
|
||||
/>
|
||||
|
||||
<Wrapper>
|
||||
<Stack justifyContent="space-between" direction="row">
|
||||
<Stack direction="row" alignItems="center" spacing={2}>
|
||||
<Avatar
|
||||
sx={{ display: 'flex', width: 50, height: 50 }}
|
||||
variant="square"
|
||||
>
|
||||
<CallSplitIcon
|
||||
fontSize="large"
|
||||
sx={{ transform: 'rotate(180deg)' }}
|
||||
/>
|
||||
</Avatar>
|
||||
{/* TODO name from path data */}
|
||||
<Typography>Paths</Typography>
|
||||
</Stack>
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={onContextMenuClick}
|
||||
ref={contextButtonRef}
|
||||
>
|
||||
<MoreHorizIcon />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Wrapper>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Bottom}
|
||||
isConnectable={false}
|
||||
style={{ visibility: 'hidden' }}
|
||||
/>
|
||||
</Box>
|
||||
{anchorEl && (
|
||||
<Menu
|
||||
open={true}
|
||||
onClose={onContextMenuClose}
|
||||
hideBackdrop={false}
|
||||
anchorEl={anchorEl}
|
||||
>
|
||||
<MenuItem onClick={deletePaths}>Delete</MenuItem>
|
||||
</Menu>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
PathsNode.propTypes = {
|
||||
data: PropTypes.shape({
|
||||
laidOut: PropTypes.bool,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default PathsNode;
|
@@ -0,0 +1,7 @@
|
||||
import { styled } from '@mui/material/styles';
|
||||
import Card from '@mui/material/Card';
|
||||
|
||||
export const Wrapper = styled(Card)`
|
||||
width: 100%;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
@@ -3,8 +3,12 @@ export const INVISIBLE_NODE_ID = 'invisible-node';
|
||||
export const NODE_TYPES = {
|
||||
FLOW_STEP: 'flowStep',
|
||||
INVISIBLE: 'invisible',
|
||||
PATHS: 'parallelPaths',
|
||||
PATH: 'path',
|
||||
};
|
||||
|
||||
export const EDGE_TYPES = {
|
||||
ADD_NODE_OR_PATHS_EDGE: 'addNodeOrPathsEdge',
|
||||
ADD_PATH_EDGE: 'addPathEdge',
|
||||
ADD_NODE_EDGE: 'addNodeEdge',
|
||||
};
|
||||
|
@@ -7,7 +7,7 @@ export const EditorWrapper = styled(Stack)(({ theme }) => ({
|
||||
flexGrow: 1,
|
||||
},
|
||||
|
||||
'& .react-flow__pane, & .react-flow__node': {
|
||||
cursor: 'auto !important',
|
||||
},
|
||||
// '& .react-flow__pane, & .react-flow__node': {
|
||||
// cursor: 'auto !important',
|
||||
// },
|
||||
}));
|
||||
|
96
packages/web/src/components/EditorNew/temp/useFlow.js
Normal file
96
packages/web/src/components/EditorNew/temp/useFlow.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import { useState } from 'react';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { insertStep } from '../utils';
|
||||
|
||||
const initFlow = {
|
||||
id: '7c55e6ce-a84a-46e3-ba31-211ec7b5c2cb',
|
||||
name: 'Name your flow',
|
||||
active: false,
|
||||
status: 'draft',
|
||||
createdAt: 1718264916266,
|
||||
updatedAt: 1718264916266,
|
||||
steps: [
|
||||
{
|
||||
id: '82ce34ab-7aab-4e6c-9f62-db5104aa81c6',
|
||||
type: 'trigger',
|
||||
key: null,
|
||||
appKey: null,
|
||||
iconUrl: null,
|
||||
webhookUrl: 'http://localhost:3000/null',
|
||||
status: 'incomplete',
|
||||
position: 1,
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: '41c60527-eb4f-4f2d-93ec-2fd37e336909',
|
||||
type: 'action',
|
||||
key: null,
|
||||
appKey: null,
|
||||
iconUrl: null,
|
||||
webhookUrl: 'http://localhost:3000/null',
|
||||
status: 'incomplete',
|
||||
position: 2,
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const generateStep = () => {
|
||||
return {
|
||||
id: uuidv4(),
|
||||
type: 'action',
|
||||
key: null,
|
||||
appKey: null,
|
||||
parameters: {},
|
||||
iconUrl: null,
|
||||
webhookUrl: 'http://localhost:3000/null',
|
||||
status: 'incomplete',
|
||||
connection: null,
|
||||
position: null,
|
||||
};
|
||||
};
|
||||
|
||||
const generatePath = (steps) => {
|
||||
return {
|
||||
id: uuidv4(),
|
||||
type: 'path',
|
||||
steps: steps?.length > 0 ? steps : [generateStep()],
|
||||
};
|
||||
};
|
||||
|
||||
export const generatePaths = (steps) => {
|
||||
return {
|
||||
id: uuidv4(),
|
||||
type: 'parallelPaths',
|
||||
steps: [generatePath(steps), generatePath()],
|
||||
};
|
||||
};
|
||||
|
||||
export const useFlow = () => {
|
||||
const [flow, setFlow] = useState(initFlow);
|
||||
|
||||
const createStep = (flow, previousStepId) => {
|
||||
const newStep = generateStep();
|
||||
const newFlow = insertStep(flow, previousStepId, newStep);
|
||||
|
||||
setFlow(newFlow);
|
||||
return newStep.id;
|
||||
};
|
||||
|
||||
const createPaths = (previousStepId) => {
|
||||
const newFlow = insertStep(flow, previousStepId, generatePaths());
|
||||
setFlow(newFlow);
|
||||
};
|
||||
|
||||
const createPath = (previousStepId) => {
|
||||
const newFlow = insertStep(flow, previousStepId, generatePath());
|
||||
setFlow(newFlow);
|
||||
};
|
||||
|
||||
return {
|
||||
flow,
|
||||
createStep,
|
||||
createPaths,
|
||||
createPath,
|
||||
};
|
||||
};
|
@@ -32,7 +32,7 @@ export const useAutoLayout = () => {
|
||||
const nodes = useNodes();
|
||||
const prevNodes = usePrevious(nodes);
|
||||
const nodesInitialized = useNodesInitialized();
|
||||
const { getEdges, setNodes, setEdges } = useReactFlow();
|
||||
const { getEdges, setNodes, setEdges, fitView } = useReactFlow();
|
||||
|
||||
const onLayout = useCallback(
|
||||
(nodes, edges) => {
|
||||
@@ -62,6 +62,8 @@ export const useAutoLayout = () => {
|
||||
prevNodes.map(({ width, height }) => ({ width, height })),
|
||||
);
|
||||
|
||||
fitView();
|
||||
|
||||
if (shouldAutoLayout) {
|
||||
onLayout(nodes, getEdges());
|
||||
}
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import { INVISIBLE_NODE_ID, NODE_TYPES } from './constants';
|
||||
import cloneDeep from 'lodash/cloneDeep.js';
|
||||
import { EDGE_TYPES, INVISIBLE_NODE_ID, NODE_TYPES } from './constants';
|
||||
|
||||
export const generateEdgeId = (sourceId, targetId) => `${sourceId}-${targetId}`;
|
||||
export const generateEdgeId = (sourceId, targetId) =>
|
||||
`${sourceId}--${targetId}`;
|
||||
|
||||
export const updatedCollapsedNodes = (nodes, openStepId) => {
|
||||
return nodes.map((node) => {
|
||||
@@ -17,11 +19,31 @@ export const updatedCollapsedNodes = (nodes, openStepId) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const generateInitialNodes = (flow) => {
|
||||
const newNodes = flow.steps.map((step, index) => {
|
||||
export const generateNodes = ({ steps, prevNodes, createdStepId }) => {
|
||||
const newNodes = steps.map((step, index) => {
|
||||
const collapsed = index !== 0;
|
||||
|
||||
return {
|
||||
const prevNode = prevNodes?.find(({ id }) => id === step.id);
|
||||
|
||||
let newNode;
|
||||
let childSteps = [];
|
||||
|
||||
switch (step.type) {
|
||||
case 'trigger':
|
||||
case 'action': {
|
||||
if (prevNode) {
|
||||
newNode = {
|
||||
...prevNode,
|
||||
zIndex: createdStepId ? 0 : prevNode?.zIndex || 0,
|
||||
data: {
|
||||
...prevNode.data,
|
||||
collapsed: createdStepId
|
||||
? true
|
||||
: prevNode?.data?.collapsed || true,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
newNode = {
|
||||
id: step.id,
|
||||
type: NODE_TYPES.FLOW_STEP,
|
||||
position: {
|
||||
@@ -34,55 +56,190 @@ export const generateInitialNodes = (flow) => {
|
||||
laidOut: false,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return [
|
||||
...newNodes,
|
||||
{
|
||||
id: INVISIBLE_NODE_ID,
|
||||
type: NODE_TYPES.INVISIBLE,
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'parallelPaths': {
|
||||
if (prevNode) {
|
||||
newNode = prevNode;
|
||||
} else {
|
||||
newNode = {
|
||||
id: step.id,
|
||||
type: NODE_TYPES.PATHS,
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
data: {
|
||||
laidOut: false,
|
||||
},
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'path': {
|
||||
if (prevNode) {
|
||||
newNode = prevNode;
|
||||
} else {
|
||||
newNode = {
|
||||
id: step.id,
|
||||
type: NODE_TYPES.PATH,
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
data: {
|
||||
laidOut: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
if (step?.steps?.length > 0) {
|
||||
childSteps = generateNodes({
|
||||
steps: step.steps,
|
||||
prevNodes,
|
||||
createdStepId,
|
||||
});
|
||||
}
|
||||
|
||||
return [newNode, ...childSteps];
|
||||
});
|
||||
|
||||
return newNodes.flat(Infinity);
|
||||
};
|
||||
|
||||
export const generateInitialEdges = (flow) => {
|
||||
const newEdges = flow.steps
|
||||
.map((step, i) => {
|
||||
export const generateEdges = ({ steps }) => {
|
||||
const newEdges = steps.map((step, index) => {
|
||||
switch (step.type) {
|
||||
case 'parallelPaths': {
|
||||
const edges = step.steps.map((childStep) => {
|
||||
const sourceId = step.id;
|
||||
const targetId = flow.steps[i + 1]?.id;
|
||||
const targetId = childStep.id;
|
||||
|
||||
const newEdge = {
|
||||
id: generateEdgeId(sourceId, targetId),
|
||||
source: sourceId,
|
||||
target: targetId,
|
||||
type: EDGE_TYPES.ADD_PATH_EDGE,
|
||||
data: {
|
||||
laidOut: false,
|
||||
},
|
||||
};
|
||||
|
||||
return newEdge;
|
||||
});
|
||||
|
||||
const childEdges = generateEdges({ steps: step.steps });
|
||||
|
||||
return [...edges, ...childEdges];
|
||||
}
|
||||
case 'path': {
|
||||
console.log({ step });
|
||||
|
||||
const sourceId = step.id;
|
||||
const targetId = step.steps?.[0]?.id;
|
||||
|
||||
if (targetId) {
|
||||
const newEdge = {
|
||||
id: generateEdgeId(sourceId, targetId),
|
||||
source: sourceId,
|
||||
target: targetId,
|
||||
type: EDGE_TYPES.ADD_NODE_EDGE,
|
||||
data: {
|
||||
laidOut: false,
|
||||
},
|
||||
};
|
||||
const childEdges = generateEdges({ steps: step.steps });
|
||||
|
||||
return [newEdge, ...childEdges];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
default: {
|
||||
const sourceId = step.id;
|
||||
const targetId = steps[index + 1]?.id;
|
||||
|
||||
if (targetId) {
|
||||
return {
|
||||
id: generateEdgeId(sourceId, targetId),
|
||||
source: sourceId,
|
||||
target: targetId,
|
||||
type: 'addNodeEdge',
|
||||
type: EDGE_TYPES.ADD_NODE_OR_PATHS_EDGE,
|
||||
data: {
|
||||
laidOut: 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: {
|
||||
laidOut: false,
|
||||
},
|
||||
},
|
||||
]
|
||||
: newEdges;
|
||||
return newEdges.flat(Infinity).filter((edge) => !!edge);
|
||||
};
|
||||
|
||||
export const findStepByStepId = (obj, id) => {
|
||||
if (Array.isArray(obj.steps)) {
|
||||
for (const step of obj.steps) {
|
||||
if (step.id === id) {
|
||||
return step;
|
||||
}
|
||||
const result = findStepByStepId(step, id);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export function insertStep(parentObj, id, newStep) {
|
||||
function recursiveFindAndInsert(parentObj, id, newStep) {
|
||||
if (parentObj.steps && Array.isArray(parentObj.steps)) {
|
||||
for (let index = 0; index < parentObj.steps.length; index++) {
|
||||
const step = parentObj.steps[index];
|
||||
if (step.id === id) {
|
||||
if (newStep.type === NODE_TYPES.PATHS) {
|
||||
const stepsAfter = parentObj.steps.slice(
|
||||
index + 1,
|
||||
parentObj.steps.length,
|
||||
);
|
||||
parentObj.steps.splice(index + 1);
|
||||
newStep.steps[0].steps = stepsAfter;
|
||||
parentObj.steps.splice(index + 1, 0, newStep);
|
||||
} else if (step.type === NODE_TYPES.PATHS) {
|
||||
step.steps.push(newStep);
|
||||
} else if (step.type === NODE_TYPES.PATH) {
|
||||
step.steps.unshift(newStep);
|
||||
} else {
|
||||
const originalSteps = step.steps || [];
|
||||
step.steps = [];
|
||||
|
||||
const newStepObject = {
|
||||
...newStep,
|
||||
steps: originalSteps,
|
||||
};
|
||||
|
||||
parentObj.steps.splice(index + 1, 0, newStepObject);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const found = recursiveFindAndInsert(step, id, newStep);
|
||||
if (found) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clone the input object to avoid mutating the original
|
||||
const newParentObj = cloneDeep(parentObj);
|
||||
recursiveFindAndInsert(newParentObj, id, newStep);
|
||||
return newParentObj;
|
||||
}
|
||||
|
@@ -105,7 +105,7 @@ function generateValidationSchema(substeps) {
|
||||
}
|
||||
|
||||
function FlowStep(props) {
|
||||
const { collapsed, onChange, onContinue, flowId } = props;
|
||||
const { collapsed, onChange, onContinue, flowId, collapseAnimation } = props;
|
||||
const editorContext = React.useContext(EditorContext);
|
||||
const contextButtonRef = React.useRef(null);
|
||||
const step = props.step;
|
||||
@@ -259,7 +259,11 @@ function FlowStep(props) {
|
||||
</Stack>
|
||||
</Header>
|
||||
|
||||
<Collapse in={!collapsed} unmountOnExit>
|
||||
<Collapse
|
||||
in={!collapsed}
|
||||
unmountOnExit
|
||||
{...(!collapseAnimation ? { timeout: 0 } : {})}
|
||||
>
|
||||
<Content>
|
||||
<List>
|
||||
<StepExecutionsProvider value={stepWithTestExecutionsData}>
|
||||
@@ -364,6 +368,11 @@ FlowStep.propTypes = {
|
||||
onClose: PropTypes.func,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onContinue: PropTypes.func,
|
||||
collapseAnimation: PropTypes.bool,
|
||||
};
|
||||
|
||||
FlowStep.defaultProps = {
|
||||
collapseAnimation: true,
|
||||
};
|
||||
|
||||
export default FlowStep;
|
||||
|
Reference in New Issue
Block a user