Baptiste Devessier
2025-09-12 13:30:31 +02:00
committed by GitHub
parent e3c84f289e
commit fffff75156
35 changed files with 375 additions and 105 deletions
@@ -12,15 +12,18 @@ import { workflowDiagramPanOnDragComponentState } from '@/workflow/workflow-diag
import { workflowDiagramWaitingNodesDimensionsComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramWaitingNodesDimensionsComponentState';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import {
type WorkflowConnection,
type WorkflowDiagram,
type WorkflowDiagramEdge,
type WorkflowDiagramEdgeType,
type WorkflowDiagramNode,
type WorkflowDiagramNodeType,
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { assertWorkflowConnectionOrThrow } from '@/workflow/workflow-diagram/utils/assertWorkflowConnectionOrThrow';
import { WorkflowDiagramConnection } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramConnection';
import { WorkflowDiagramCustomMarkers } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramCustomMarkers';
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
import { type WorkflowDiagramEdgeComponentProps } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdgeComponentProps';
import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
@@ -32,7 +35,6 @@ import {
useReactFlow,
type Connection,
type EdgeChange,
type EdgeProps,
type FitViewOptions,
type NodeChange,
type NodeProps,
@@ -119,7 +121,7 @@ export const WorkflowDiagramCanvasBase = ({
Record<
WorkflowDiagramEdgeType,
React.ComponentType<
EdgeProps & {
WorkflowDiagramEdgeComponentProps & {
data: any;
type: any;
}
@@ -131,7 +133,7 @@ export const WorkflowDiagramCanvasBase = ({
tagColor: TagColor;
tagText: string;
onInit?: () => void;
onConnect?: (params: Connection) => void;
onConnect?: (params: WorkflowConnection) => void;
onDeleteEdge?: (edge: WorkflowDiagramEdge) => void;
onNodeDragStop?: OnNodeDrag<WorkflowDiagramNode>;
nodesConnectable?: boolean;
@@ -438,6 +440,8 @@ export const WorkflowDiagramCanvasBase = ({
setEdgeHovered({
source: hoveredEdge.source,
target: hoveredEdge.target,
sourceHandle: hoveredEdge.sourceHandle,
targetHandle: hoveredEdge.targetHandle,
});
},
[setEdgeHovered],
@@ -447,6 +451,12 @@ export const WorkflowDiagramCanvasBase = ({
clearEdgeHover();
}, [clearEdgeHover]);
const handleConnect = (connection: Connection) => {
assertWorkflowConnectionOrThrow(connection);
onConnect?.(connection);
};
return (
<StyledResetReactflowStyles ref={containerRef}>
<WorkflowDiagramCustomMarkers />
@@ -457,6 +467,7 @@ export const WorkflowDiagramCanvasBase = ({
maxZoom={defaultFitViewOptions.maxZoom}
defaultViewport={{ x: 0, y: 150, zoom: defaultFitViewOptions.maxZoom }}
nodeTypes={nodeTypes}
// @ts-expect-error We override Reactflow types for sourceHandle and targetHandle to be required
edgeTypes={edgeTypes}
nodes={nodes}
edges={edges}
@@ -464,7 +475,7 @@ export const WorkflowDiagramCanvasBase = ({
onEdgeMouseLeave={onEdgeMouseLeave}
onNodesChange={handleNodesChanges}
onEdgesChange={handleEdgesChange}
onConnect={onConnect}
onConnect={handleConnect}
onNodeDragStop={onNodeDragStop}
onBeforeDelete={onBeforeDelete}
onDelete={onDelete}
@@ -7,6 +7,7 @@ import { WorkflowDiagramCanvasEditableEffect } from '@/workflow/workflow-diagram
import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState';
import { workflowDiagramRightClickMenuPositionState } from '@/workflow/workflow-diagram/states/workflowDiagramRightClickMenuPositionState';
import {
type WorkflowConnection,
type WorkflowDiagramEdge,
type WorkflowDiagramNode,
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
@@ -21,7 +22,7 @@ import { useCreateEdge } from '@/workflow/workflow-steps/hooks/useCreateEdge';
import { useDeleteEdge } from '@/workflow/workflow-steps/hooks/useDeleteEdge';
import { useUpdateStep } from '@/workflow/workflow-steps/hooks/useUpdateStep';
import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger';
import { addEdge, type Connection, ReactFlowProvider } from '@xyflow/react';
import { addEdge, ReactFlowProvider } from '@xyflow/react';
import React from 'react';
import { isDefined } from 'twenty-shared/utils';
@@ -50,7 +51,7 @@ export const WorkflowDiagramCanvasEditable = () => {
const { updateTrigger } = useUpdateWorkflowVersionTrigger();
const onConnect = (edgeConnect: Connection) => {
const onConnect = (edgeConnect: WorkflowConnection) => {
setWorkflowDiagram((diagram) => {
if (isDefined(diagram) === false) {
throw new Error(
@@ -60,7 +61,7 @@ export const WorkflowDiagramCanvasEditable = () => {
return {
...diagram,
edges: addEdge(edgeConnect, diagram.edges),
edges: addEdge<WorkflowDiagramEdge>(edgeConnect, diagram.edges),
};
});
@@ -68,7 +69,12 @@ export const WorkflowDiagramCanvasEditable = () => {
};
const onDeleteEdge = async (edge: WorkflowDiagramEdge) => {
await deleteEdge(edge);
await deleteEdge({
source: edge.source,
target: edge.target,
sourceHandle: edge.sourceHandle,
targetHandle: edge.targetHandle,
});
};
const onNodeDragStop = async (
@@ -1,3 +1,4 @@
import { assertEdgeHasDefinedHandlesOrThrow } from '@/workflow/workflow-diagram/utils/assertEdgeHasDefinedHandlesOrThrow';
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
import {
type OnSelectionChangeParams,
@@ -19,9 +20,13 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
return;
}
assertEdgeHasDefinedHandlesOrThrow(selectedEdge);
setEdgeSelected({
source: selectedEdge.source,
target: selectedEdge.target,
sourceHandle: selectedEdge.sourceHandle,
targetHandle: selectedEdge.targetHandle,
});
},
[setEdgeSelected, clearEdgeSelected],
@@ -6,12 +6,25 @@ import {
import { type WorkflowDiagramIteratorEmptyActionNodeData } from '@/workflow/workflow-diagram/workflow-iterator/types/WorkflowDiagramIteratorEmptyActionNodeData';
import { type FilterSettings } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowEditActionFilter';
import { type MessageDescriptor } from '@lingui/core';
import { type Edge, type Node, type Position } from '@xyflow/react';
import {
type Connection,
type Edge,
type Node,
type Position,
} from '@xyflow/react';
import { type StepStatus } from 'twenty-shared/workflow';
export type WorkflowDiagramStepNode = Node<WorkflowDiagramStepNodeData>;
export type WorkflowDiagramNode = Node<WorkflowDiagramNodeData>;
export type WorkflowDiagramEdge = Edge<WorkflowDiagramEdgeData>;
export type WorkflowDiagramEdge = Edge<WorkflowDiagramEdgeData> & {
sourceHandle: string;
targetHandle: string;
};
export type WorkflowConnection = Connection & {
sourceHandle: string;
targetHandle: string;
};
export type WorkflowRunDiagramNode = Node<WorkflowRunDiagramNodeData>;
@@ -121,7 +121,9 @@ describe('generateWorkflowRunDiagram', () => {
"markerStart": undefined,
"selectable": false,
"source": "trigger",
"sourceHandle": "default",
"target": "step1",
"targetHandle": "default",
"type": "empty-filter--run",
"zIndex": -2,
},
@@ -136,7 +138,9 @@ describe('generateWorkflowRunDiagram', () => {
"markerStart": undefined,
"selectable": false,
"source": "step1",
"sourceHandle": "default",
"target": "step2",
"targetHandle": "default",
"type": "empty-filter--run",
"zIndex": -2,
},
@@ -151,7 +155,9 @@ describe('generateWorkflowRunDiagram', () => {
"markerStart": undefined,
"selectable": false,
"source": "step2",
"sourceHandle": "default",
"target": "step3",
"targetHandle": "default",
"type": "empty-filter--run",
"zIndex": -2,
},
@@ -352,7 +358,9 @@ describe('generateWorkflowRunDiagram', () => {
"markerStart": undefined,
"selectable": false,
"source": "trigger",
"sourceHandle": "default",
"target": "step1",
"targetHandle": "default",
"type": "empty-filter--run",
"zIndex": -2,
},
@@ -367,7 +375,9 @@ describe('generateWorkflowRunDiagram', () => {
"markerStart": undefined,
"selectable": false,
"source": "step1",
"sourceHandle": "default",
"target": "step2",
"targetHandle": "default",
"type": "empty-filter--run",
"zIndex": -2,
},
@@ -382,7 +392,9 @@ describe('generateWorkflowRunDiagram', () => {
"markerStart": undefined,
"selectable": false,
"source": "step2",
"sourceHandle": "default",
"target": "step3",
"targetHandle": "default",
"type": "empty-filter--run",
"zIndex": -2,
},
@@ -583,7 +595,9 @@ describe('generateWorkflowRunDiagram', () => {
"markerStart": undefined,
"selectable": false,
"source": "trigger",
"sourceHandle": "default",
"target": "step1",
"targetHandle": "default",
"type": "empty-filter--run",
"zIndex": -2,
},
@@ -598,7 +612,9 @@ describe('generateWorkflowRunDiagram', () => {
"markerStart": undefined,
"selectable": false,
"source": "step1",
"sourceHandle": "default",
"target": "step2",
"targetHandle": "default",
"type": "empty-filter--run",
"zIndex": -2,
},
@@ -613,7 +629,9 @@ describe('generateWorkflowRunDiagram', () => {
"markerStart": undefined,
"selectable": false,
"source": "step2",
"sourceHandle": "default",
"target": "step3",
"targetHandle": "default",
"type": "empty-filter--run",
"zIndex": -2,
},
@@ -833,7 +851,9 @@ describe('generateWorkflowRunDiagram', () => {
"markerStart": undefined,
"selectable": false,
"source": "trigger",
"sourceHandle": "default",
"target": "step1",
"targetHandle": "default",
"type": "empty-filter--run",
"zIndex": -2,
},
@@ -848,7 +868,9 @@ describe('generateWorkflowRunDiagram', () => {
"markerStart": undefined,
"selectable": false,
"source": "step1",
"sourceHandle": "default",
"target": "step2",
"targetHandle": "default",
"type": "empty-filter--run",
"zIndex": -2,
},
@@ -863,7 +885,9 @@ describe('generateWorkflowRunDiagram', () => {
"markerStart": undefined,
"selectable": false,
"source": "step2",
"sourceHandle": "default",
"target": "step3",
"targetHandle": "default",
"type": "empty-filter--run",
"zIndex": -2,
},
@@ -878,7 +902,9 @@ describe('generateWorkflowRunDiagram', () => {
"markerStart": undefined,
"selectable": false,
"source": "step3",
"sourceHandle": "default",
"target": "step4",
"targetHandle": "default",
"type": "empty-filter--run",
"zIndex": -2,
},
@@ -1064,7 +1090,9 @@ describe('generateWorkflowRunDiagram', () => {
"markerStart": undefined,
"selectable": false,
"source": "trigger",
"sourceHandle": "default",
"target": "step1",
"targetHandle": "default",
"type": "empty-filter--run",
"zIndex": -2,
},
@@ -161,7 +161,9 @@ describe('getWorkflowVersionDiagram', () => {
"markerStart": undefined,
"selectable": true,
"source": "trigger",
"sourceHandle": "default",
"target": "step-1",
"targetHandle": "default",
"type": "empty-filter--editable",
"zIndex": -2,
},
@@ -34,7 +34,9 @@ describe('transformFilterNodesAsEdges', () => {
{
id: 'A-C',
source: 'A',
sourceHandle: 'default',
target: 'C',
targetHandle: 'default',
data: {
edgeType: 'default',
},
@@ -96,13 +98,17 @@ describe('transformFilterNodesAsEdges', () => {
{
id: 'A-B',
source: 'A',
sourceHandle: 'default',
target: 'B',
targetHandle: 'default',
data: { edgeType: 'default' },
},
{
id: 'B-C',
source: 'B',
sourceHandle: 'default',
target: 'C',
targetHandle: 'default',
data: { edgeType: 'default' },
},
],
@@ -148,7 +154,9 @@ describe('transformFilterNodesAsEdges', () => {
id: 'A-C-filter-B',
type: 'filter--editable',
source: 'A',
sourceHandle: 'default',
target: 'C',
targetHandle: 'default',
selectable: true,
deletable: true,
data: {
@@ -229,25 +237,33 @@ describe('transformFilterNodesAsEdges', () => {
{
id: 'A-B1',
source: 'A',
sourceHandle: 'default',
target: 'B1',
targetHandle: 'default',
data: { edgeType: 'default' },
},
{
id: 'B1-C',
source: 'B1',
sourceHandle: 'default',
target: 'C',
targetHandle: 'default',
data: { edgeType: 'default' },
},
{
id: 'C-B2',
source: 'C',
sourceHandle: 'default',
target: 'B2',
targetHandle: 'default',
data: { edgeType: 'default' },
},
{
id: 'B2-D',
source: 'B2',
sourceHandle: 'default',
target: 'D',
targetHandle: 'default',
data: { edgeType: 'default' },
},
],
@@ -275,7 +291,9 @@ describe('transformFilterNodesAsEdges', () => {
id: 'A-C-filter-B1',
type: 'filter--editable',
source: 'A',
sourceHandle: 'default',
target: 'C',
targetHandle: 'default',
selectable: true,
deletable: true,
data: {
@@ -294,7 +312,9 @@ describe('transformFilterNodesAsEdges', () => {
id: 'C-D-filter-B2',
type: 'filter--editable',
source: 'C',
sourceHandle: 'default',
target: 'D',
targetHandle: 'default',
selectable: true,
deletable: true,
data: {
@@ -339,7 +359,9 @@ describe('transformFilterNodesAsEdges', () => {
{
id: 'A-B',
source: 'A',
sourceHandle: 'default',
target: 'B',
targetHandle: 'default',
data: { edgeType: 'default' },
},
],
@@ -415,13 +437,17 @@ describe('transformFilterNodesAsEdges', () => {
{
id: 'trigger-B',
source: 'trigger',
sourceHandle: 'default',
target: 'B',
targetHandle: 'default',
data: { edgeType: 'default' },
},
{
id: 'B-C',
source: 'B',
sourceHandle: 'default',
target: 'C',
targetHandle: 'default',
data: { edgeType: 'default' },
},
],
@@ -467,7 +493,9 @@ describe('transformFilterNodesAsEdges', () => {
id: 'trigger-C-filter-B',
type: 'filter--editable',
source: 'trigger',
sourceHandle: 'default',
target: 'C',
targetHandle: 'default',
selectable: true,
deletable: true,
data: {
@@ -0,0 +1,18 @@
import { type Edge } from '@xyflow/react';
import { isDefined } from 'twenty-shared/utils';
type AssertEdgeHasDefinedHandlesOrThrow = (
edge: Edge,
) => asserts edge is Edge & {
sourceHandle: string;
targetHandle: string;
};
export const assertEdgeHasDefinedHandlesOrThrow: AssertEdgeHasDefinedHandlesOrThrow =
(edge) => {
if (!isDefined(edge.sourceHandle) || !isDefined(edge.targetHandle)) {
throw new Error(
'sourceHandle and targetHandle must be defined on the edge.',
);
}
};
@@ -0,0 +1,19 @@
import { type WorkflowConnection } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { type Connection } from '@xyflow/react';
import { isDefined } from 'twenty-shared/utils';
type AssertWorkflowConnectionOrThrow = (
connection: Connection,
) => asserts connection is WorkflowConnection;
export const assertWorkflowConnectionOrThrow: AssertWorkflowConnectionOrThrow =
(connection) => {
if (
!isDefined(connection.sourceHandle) ||
!isDefined(connection.targetHandle)
) {
throw new Error(
'WorkflowConnection must have defined sourceHandle and targetHandle.',
);
}
};
@@ -7,6 +7,8 @@ import {
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { getEdgeTypeBetweenTwoNodes } from '@/workflow/workflow-diagram/utils/getEdgeTypeBetweenTwoNodes';
import { WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION } from '@/workflow/workflow-diagram/workflow-edges/constants/WorkflowVisualizerEdgeDefaultConfiguration';
import { WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID } from '@/workflow/workflow-diagram/workflow-nodes/constants/WorkflowDiagramNodeDefaultSourceHandleId';
import { WORKFLOW_DIAGRAM_NODE_DEFAULT_TARGET_HANDLE_ID } from '@/workflow/workflow-diagram/workflow-nodes/constants/WorkflowDiagramNodeDefaultTargetHandleId';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
@@ -61,7 +63,9 @@ export const generateNodesAndEdgesForDefaultNode = ({
type: edgeTypeBetweenTwoNodes,
id: v4(),
source: step.id,
sourceHandle: WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID,
target: child,
targetHandle: WORKFLOW_DIAGRAM_NODE_DEFAULT_TARGET_HANDLE_ID,
...(edgeTypeBetweenTwoNodes.includes('editable')
? { deletable: true, selectable: true }
: {}),
@@ -9,6 +9,8 @@ import { getEdgeTypeBetweenTwoNodes } from '@/workflow/workflow-diagram/utils/ge
import { WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION } from '@/workflow/workflow-diagram/workflow-edges/constants/WorkflowVisualizerEdgeDefaultConfiguration';
import { type WorkflowDiagramIteratorEmptyActionNodeData } from '@/workflow/workflow-diagram/workflow-iterator/types/WorkflowDiagramIteratorEmptyActionNodeData';
import { WORKFLOW_DIAGRAM_ITERATOR_NODE_LOOP_HANDLE_ID } from '@/workflow/workflow-diagram/workflow-nodes/constants/WorkflowDiagramIteratorNodeLoopHandleId';
import { WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID } from '@/workflow/workflow-diagram/workflow-nodes/constants/WorkflowDiagramNodeDefaultSourceHandleId';
import { WORKFLOW_DIAGRAM_NODE_DEFAULT_TARGET_HANDLE_ID } from '@/workflow/workflow-diagram/workflow-nodes/constants/WorkflowDiagramNodeDefaultTargetHandleId';
import { msg } from '@lingui/core/macro';
import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
import { Position } from '@xyflow/react';
@@ -88,6 +90,7 @@ export const generateNodesAndEdgesForIteratorNode = ({
...(edgeTypeBetweenTwoNodes.includes('editable')
? { deletable: true, selectable: true }
: {}),
targetHandle: WORKFLOW_DIAGRAM_NODE_DEFAULT_TARGET_HANDLE_ID,
data: {
...WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION.data,
labelOptions: {
@@ -128,6 +131,7 @@ export const generateNodesAndEdgesForIteratorNode = ({
...(edgeTypeBetweenTwoNodes.includes('editable')
? { deletable: true, selectable: true }
: {}),
targetHandle: WORKFLOW_DIAGRAM_NODE_DEFAULT_TARGET_HANDLE_ID,
data: {
...WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION.data,
labelOptions: {
@@ -142,10 +146,12 @@ export const generateNodesAndEdgesForIteratorNode = ({
type: edgeTypeBetweenTwoNodes,
id: v4(),
source: emptyNodeId,
sourceHandle: WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID,
target: step.id,
...(edgeTypeBetweenTwoNodes.includes('editable')
? { deletable: true, selectable: true }
: {}),
targetHandle: WORKFLOW_DIAGRAM_NODE_DEFAULT_TARGET_HANDLE_ID,
});
}
@@ -155,10 +161,12 @@ export const generateNodesAndEdgesForIteratorNode = ({
type: edgeTypeBetweenTwoNodes,
id: v4(),
source: step.id,
sourceHandle: WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID,
target: child,
...(edgeTypeBetweenTwoNodes.includes('editable')
? { deletable: true, selectable: true }
: {}),
targetHandle: WORKFLOW_DIAGRAM_NODE_DEFAULT_TARGET_HANDLE_ID,
data: {
...WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION.data,
labelOptions: {
@@ -16,6 +16,8 @@ import { generateNodesAndEdgesForIteratorNode } from '@/workflow/workflow-diagra
import { getEdgeTypeBetweenTwoNodes } from '@/workflow/workflow-diagram/utils/getEdgeTypeBetweenTwoNodes';
import { getWorkflowDiagramTriggerNode } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramTriggerNode';
import { WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION } from '@/workflow/workflow-diagram/workflow-edges/constants/WorkflowVisualizerEdgeDefaultConfiguration';
import { WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID } from '@/workflow/workflow-diagram/workflow-nodes/constants/WorkflowDiagramNodeDefaultSourceHandleId';
import { WORKFLOW_DIAGRAM_NODE_DEFAULT_TARGET_HANDLE_ID } from '@/workflow/workflow-diagram/workflow-nodes/constants/WorkflowDiagramNodeDefaultTargetHandleId';
import { isDefined } from 'twenty-shared/utils';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
import { v4 } from 'uuid';
@@ -48,10 +50,12 @@ export const generateWorkflowDiagram = ({
type: edgeTypeBetweenTwoNodes,
id: v4(),
source: TRIGGER_STEP_ID,
sourceHandle: WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID,
target: stepLinkToTriggerId,
...(edgeTypeBetweenTwoNodes.includes('editable')
? { deletable: true, selectable: true }
: {}),
targetHandle: WORKFLOW_DIAGRAM_NODE_DEFAULT_TARGET_HANDLE_ID,
});
}
@@ -1,11 +1,16 @@
import { useTheme } from '@emotion/react';
import { BaseEdge, type EdgeProps } from '@xyflow/react';
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
import { type WorkflowDiagramEdgeComponentProps } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdgeComponentProps';
import { useTheme } from '@emotion/react';
import { BaseEdge } from '@xyflow/react';
type WorkflowDiagramBaseEdgeProps = Pick<
EdgeProps<WorkflowDiagramEdge>,
'source' | 'target' | 'markerStart' | 'markerEnd'
WorkflowDiagramEdgeComponentProps,
| 'source'
| 'sourceHandleId'
| 'target'
| 'targetHandleId'
| 'markerStart'
| 'markerEnd'
> & {
path: string;
};
@@ -13,6 +18,8 @@ type WorkflowDiagramBaseEdgeProps = Pick<
export const WorkflowDiagramBaseEdge = ({
source,
target,
sourceHandleId,
targetHandleId,
markerStart,
markerEnd,
path,
@@ -21,9 +28,19 @@ export const WorkflowDiagramBaseEdge = ({
const { isEdgeSelected, isEdgeHovered } = useEdgeState();
const selected = isEdgeSelected({ source, target });
const selected = isEdgeSelected({
source,
target,
sourceHandle: sourceHandleId,
targetHandle: targetHandleId,
});
const isHovered = isEdgeHovered({ source, target });
const isHovered = isEdgeHovered({
source,
target,
sourceHandle: sourceHandleId,
targetHandle: targetHandleId,
});
const stroke = selected
? theme.color.blue
@@ -2,7 +2,6 @@ import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { useOpenWorkflowEditFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowEditFilterInCommandMenu';
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { WorkflowDiagramBaseEdge } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramBaseEdge';
import { WorkflowDiagramEdgeButtonGroup } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeButtonGroup';
import { WorkflowDiagramEdgeLabel } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeLabel';
@@ -11,24 +10,24 @@ import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/work
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2VisibilityContainer';
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/workflow-edges/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
import { type WorkflowDiagramEdgeComponentProps } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdgeComponentProps';
import { useCreateStep } from '@/workflow/workflow-steps/hooks/useCreateStep';
import { useDeleteEdge } from '@/workflow/workflow-steps/hooks/useDeleteEdge';
import { useLingui } from '@lingui/react/macro';
import {
EdgeLabelRenderer,
type EdgeProps,
getBezierPath,
} from '@xyflow/react';
import { EdgeLabelRenderer, getBezierPath } from '@xyflow/react';
import { type MouseEvent, useContext } from 'react';
import { useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { IconFilter, IconPlus, IconTrash } from 'twenty-ui/display';
type WorkflowDiagramDefaultEdgeEditableProps = EdgeProps<WorkflowDiagramEdge>;
type WorkflowDiagramDefaultEdgeEditableProps =
WorkflowDiagramEdgeComponentProps;
export const WorkflowDiagramDefaultEdgeEditable = ({
source,
sourceHandleId,
target,
targetHandleId,
sourceX,
sourceY,
targetX,
@@ -100,14 +99,21 @@ export const WorkflowDiagramDefaultEdgeEditable = ({
const handleDeleteBranch = async (event: MouseEvent) => {
event.stopPropagation();
await deleteEdge({ source, target });
await deleteEdge({
source,
sourceHandle: sourceHandleId,
target,
targetHandle: targetHandleId,
});
};
return (
<>
<WorkflowDiagramBaseEdge
source={source}
sourceHandleId={sourceHandleId}
target={target}
targetHandleId={targetHandleId}
path={edgePath}
markerStart={markerStart}
markerEnd={markerEnd}
@@ -131,7 +137,13 @@ export const WorkflowDiagramDefaultEdgeEditable = ({
>
<WorkflowDiagramEdgeV2VisibilityContainer
shouldDisplay={
nodeCreationStarted || isEdgeHovered({ source, target })
nodeCreationStarted ||
isEdgeHovered({
source,
target,
sourceHandle: sourceHandleId,
targetHandle: targetHandleId,
})
}
>
<WorkflowDiagramEdgeButtonGroup
@@ -1,8 +1,9 @@
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { type WorkflowDiagramEdgeComponentProps } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdgeComponentProps';
import { useTheme } from '@emotion/react';
import { BaseEdge, type EdgeProps, getBezierPath } from '@xyflow/react';
import { BaseEdge, getBezierPath } from '@xyflow/react';
type WorkflowDiagramDefaultEdgeReadonlyProps = EdgeProps<WorkflowDiagramEdge>;
type WorkflowDiagramDefaultEdgeReadonlyProps =
WorkflowDiagramEdgeComponentProps;
export const WorkflowDiagramDefaultEdgeReadonly = ({
sourceX,
@@ -1,8 +1,8 @@
import { WorkflowRunDiagramBaseEdge } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowRunDiagramBaseEdge';
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { type EdgeProps, getBezierPath } from '@xyflow/react';
import { type WorkflowDiagramEdgeComponentProps } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdgeComponentProps';
import { getBezierPath } from '@xyflow/react';
type WorkflowDiagramDefaultEdgeRunProps = EdgeProps<WorkflowDiagramEdge>;
type WorkflowDiagramDefaultEdgeRunProps = WorkflowDiagramEdgeComponentProps;
export const WorkflowDiagramDefaultEdgeRun = ({
sourceX,
@@ -13,10 +13,7 @@ import { useOpenWorkflowEditFilterInCommandMenu } from '@/workflow/workflow-diag
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
import { workflowDiagramPanOnDragComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramPanOnDragComponentState';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import {
type WorkflowDiagramEdge,
type WorkflowDiagramEdgeData,
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { type WorkflowDiagramEdgeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
import { WorkflowDiagramBaseEdge } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramBaseEdge';
import { WorkflowDiagramEdgeButtonGroup } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeButtonGroup';
@@ -26,6 +23,7 @@ import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/work
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2VisibilityContainer';
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/workflow-edges/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
import { type WorkflowDiagramEdgeComponentProps } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdgeComponentProps';
import { useDeleteEdge } from '@/workflow/workflow-steps/hooks/useDeleteEdge';
import { useDeleteStep } from '@/workflow/workflow-steps/hooks/useDeleteStep';
import { WorkflowStepFilterCounter } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterCounter';
@@ -34,11 +32,7 @@ import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { isNonEmptyString } from '@sniptt/guards';
import {
EdgeLabelRenderer,
type EdgeProps,
getBezierPath,
} from '@xyflow/react';
import { EdgeLabelRenderer, getBezierPath } from '@xyflow/react';
import { useContext } from 'react';
import { useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
@@ -53,7 +47,7 @@ import {
import { IconButtonGroup } from 'twenty-ui/input';
import { MenuItem } from 'twenty-ui/navigation';
type WorkflowDiagramFilterEdgeEditableProps = EdgeProps<WorkflowDiagramEdge>;
type WorkflowDiagramFilterEdgeEditableProps = WorkflowDiagramEdgeComponentProps;
const assertFilterEdgeDataOrThrow: (
data: WorkflowDiagramEdgeData | undefined,
@@ -86,7 +80,9 @@ const StyledIconButtonGroup = styled(IconButtonGroup)<{ selected?: boolean }>`
export const WorkflowDiagramFilterEdgeEditable = ({
source,
sourceHandleId,
target,
targetHandleId,
sourceY,
sourceX,
targetY,
@@ -179,7 +175,12 @@ export const WorkflowDiagramFilterEdgeEditable = ({
const handleDeleteBranchClick = async () => {
closeDropdown(dropdownId);
await deleteEdge({ source, target });
await deleteEdge({
source,
sourceHandle: sourceHandleId,
target,
targetHandle: targetHandleId,
});
};
const { filterCounter } = useFilterCounter({ stepId: data.stepId });
@@ -189,7 +190,9 @@ export const WorkflowDiagramFilterEdgeEditable = ({
<>
<WorkflowDiagramBaseEdge
source={source}
sourceHandleId={sourceHandleId}
target={target}
targetHandleId={targetHandleId}
path={edgePath}
markerStart={markerStart}
markerEnd={markerEnd}
@@ -218,7 +221,12 @@ export const WorkflowDiagramFilterEdgeEditable = ({
textColor={theme.font.color.inverted}
counter={filterCounter}
/>
{isEdgeHovered({ source, target }) ||
{isEdgeHovered({
source,
target,
sourceHandle: sourceHandleId,
targetHandle: targetHandleId,
}) ||
isDropdownOpen ||
nodeCreationStarted ? (
<WorkflowDiagramEdgeButtonGroup
@@ -3,31 +3,24 @@ import { commandMenuNavigationStackState } from '@/command-menu/states/commandMe
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useOpenWorkflowViewFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowViewFilterInCommandMenu';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import {
type WorkflowDiagramEdge,
type WorkflowDiagramEdgeData,
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { type WorkflowDiagramEdgeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2Container';
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2VisibilityContainer';
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/workflow-edges/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
import { type WorkflowDiagramEdgeComponentProps } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdgeComponentProps';
import { WorkflowStepFilterCounter } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterCounter';
import { useFilterCounter } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useFilterCounter';
import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
import {
BaseEdge,
EdgeLabelRenderer,
type EdgeProps,
getBezierPath,
} from '@xyflow/react';
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from '@xyflow/react';
import { useContext } from 'react';
import { useSetRecoilState } from 'recoil';
import { IconFilter } from 'twenty-ui/display';
import { IconButtonGroup } from 'twenty-ui/input';
type WorkflowDiagramFilterEdgeReadonlyProps = EdgeProps<WorkflowDiagramEdge>;
type WorkflowDiagramFilterEdgeReadonlyProps = WorkflowDiagramEdgeComponentProps;
const assertFilterEdgeDataOrThrow: (
data: WorkflowDiagramEdgeData | undefined,
@@ -2,30 +2,24 @@ import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/ho
import type { WorkflowRunStepStatus } from '@/workflow/types/Workflow';
import { useOpenWorkflowRunFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowRunFilterInCommandMenu';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import {
type WorkflowDiagramEdge,
type WorkflowDiagramEdgeData,
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { type WorkflowDiagramEdgeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2Container';
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2VisibilityContainer';
import { WorkflowRunDiagramBaseEdge } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowRunDiagramBaseEdge';
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/workflow-edges/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
import { type WorkflowDiagramEdgeComponentProps } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdgeComponentProps';
import { WorkflowStepFilterCounter } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterCounter';
import { useFilterCounter } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useFilterCounter';
import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
import {
EdgeLabelRenderer,
type EdgeProps,
getBezierPath,
} from '@xyflow/react';
import { EdgeLabelRenderer, getBezierPath } from '@xyflow/react';
import { isDefined } from 'twenty-shared/utils';
import { IconFilter } from 'twenty-ui/display';
import { IconButtonGroup } from 'twenty-ui/input';
type WorkflowDiagramFilterEdgeRunProps = EdgeProps<WorkflowDiagramEdge>;
type WorkflowDiagramFilterEdgeRunProps = WorkflowDiagramEdgeComponentProps;
const assertFilterEdgeDataOrThrow: (
data: WorkflowDiagramEdgeData | undefined,
@@ -1,9 +1,9 @@
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { type WorkflowDiagramEdgeComponentProps } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdgeComponentProps';
import { useTheme } from '@emotion/react';
import { BaseEdge, type EdgeProps } from '@xyflow/react';
import { BaseEdge } from '@xyflow/react';
type WorkflowRunDiagramBaseEdgeProps = Pick<
EdgeProps<WorkflowDiagramEdge>,
WorkflowDiagramEdgeComponentProps,
'markerStart' | 'markerEnd'
> & {
path: string;
@@ -1,9 +1,9 @@
import { workflowSelectedEdgeComponentState } from '@/workflow/workflow-diagram/workflow-edges/states/workflowSelectedEdgeComponentState';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdge';
import { workflowHoveredEdgeComponentState } from '@/workflow/workflow-diagram/workflow-edges/states/workflowHoveredEdgeComponentState';
import { useReactFlow } from '@xyflow/react';
import { EDGE_BRANCH_ARROW_MARKER } from '@/workflow/workflow-diagram/workflow-edges/constants/EdgeBranchArrowMarker';
import { workflowHoveredEdgeComponentState } from '@/workflow/workflow-diagram/workflow-edges/states/workflowHoveredEdgeComponentState';
import { workflowSelectedEdgeComponentState } from '@/workflow/workflow-diagram/workflow-edges/states/workflowSelectedEdgeComponentState';
import { type WorkflowDiagramEdgeDescriptor } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdgeDescriptor';
import { useReactFlow } from '@xyflow/react';
export const useEdgeState = () => {
const reactflow = useReactFlow();
@@ -15,32 +15,73 @@ export const useEdgeState = () => {
workflowHoveredEdgeComponentState,
);
const isSourceSelected = (nodeId: string) => {
return workflowSelectedEdge?.source === nodeId;
const isSourceSelected = ({
nodeId,
sourceHandle,
}: {
nodeId: string;
sourceHandle: string | null | undefined;
}) => {
return (
workflowSelectedEdge?.source === nodeId &&
workflowSelectedEdge.sourceHandle === sourceHandle
);
};
const isSourceHovered = (nodeId: string) => {
return workflowHoveredEdge?.source === nodeId;
const isSourceHovered = ({
nodeId,
sourceHandle,
}: {
nodeId: string;
sourceHandle: string | null | undefined;
}) => {
return (
workflowHoveredEdge?.source === nodeId &&
workflowHoveredEdge.sourceHandle === sourceHandle
);
};
const isEdgeSelected = ({ source, target }: WorkflowDiagramEdge) => {
return isSourceSelected(source) && workflowSelectedEdge?.target === target;
const isEdgeSelected = ({
source,
target,
sourceHandle,
targetHandle,
}: WorkflowDiagramEdgeDescriptor) => {
return (
isSourceSelected({ nodeId: source, sourceHandle }) &&
workflowSelectedEdge?.target === target &&
workflowSelectedEdge.targetHandle === targetHandle
);
};
const isEdgeHovered = ({ source, target }: WorkflowDiagramEdge) => {
return isSourceHovered(source) && workflowHoveredEdge?.target === target;
const isEdgeHovered = ({
source,
target,
sourceHandle,
targetHandle,
}: WorkflowDiagramEdgeDescriptor) => {
return (
isSourceHovered({ nodeId: source, sourceHandle }) &&
workflowHoveredEdge?.target === target &&
workflowHoveredEdge.targetHandle === targetHandle
);
};
const setEdgeSelected = ({ source, target }: WorkflowDiagramEdge) => {
if (isEdgeSelected({ source, target })) {
const setEdgeSelected = (edgeDescriptor: WorkflowDiagramEdgeDescriptor) => {
if (isEdgeSelected(edgeDescriptor)) {
return;
}
setWorkflowSelectedEdge({ source, target });
setWorkflowSelectedEdge(edgeDescriptor);
reactflow.setEdges((edges) =>
edges.map((edge) => {
if (edge.source === source && edge.target === target) {
if (
edge.source === edgeDescriptor.source &&
edge.sourceHandle === edgeDescriptor.sourceHandle &&
edge.target === edgeDescriptor.target &&
edge.targetHandle === edgeDescriptor.targetHandle
) {
return {
...edge,
...EDGE_BRANCH_ARROW_MARKER.Selected,
@@ -55,14 +96,21 @@ export const useEdgeState = () => {
);
};
const setEdgeHovered = ({ source, target }: WorkflowDiagramEdge) => {
setWorkflowHoveredEdge({ source, target });
const setEdgeHovered = ({
source,
target,
sourceHandle,
targetHandle,
}: WorkflowDiagramEdgeDescriptor) => {
setWorkflowHoveredEdge({ source, target, sourceHandle, targetHandle });
reactflow.setEdges((edges) =>
edges.map((edge) => {
if (
edge.source === source &&
edge.sourceHandle === sourceHandle &&
edge.target === target &&
edge.targetHandle === targetHandle &&
edge.markerEnd !== EDGE_BRANCH_ARROW_MARKER.Selected.markerEnd
) {
return {
@@ -1,9 +1,9 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdge';
import { type WorkflowDiagramEdgeDescriptor } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdgeDescriptor';
export const workflowHoveredEdgeComponentState = createComponentState<
WorkflowDiagramEdge | undefined
WorkflowDiagramEdgeDescriptor | undefined
>({
key: 'workflowHoveredEdgeComponentState',
defaultValue: undefined,
@@ -1,9 +1,9 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdge';
import { type WorkflowDiagramEdgeDescriptor } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdgeDescriptor';
export const workflowSelectedEdgeComponentState = createComponentState<
WorkflowDiagramEdge | undefined
WorkflowDiagramEdgeDescriptor | undefined
>({
key: 'workflowSelectedEdgeComponentState',
defaultValue: undefined,
@@ -1,4 +0,0 @@
export type WorkflowDiagramEdge = {
source: string;
target: string;
};
@@ -0,0 +1,8 @@
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { type EdgeProps } from '@xyflow/react';
export type WorkflowDiagramEdgeComponentProps =
EdgeProps<WorkflowDiagramEdge> & {
sourceHandleId: string;
targetHandleId: string;
};
@@ -0,0 +1,6 @@
export type WorkflowDiagramEdgeDescriptor = {
source: string;
target: string;
sourceHandle: string;
targetHandle: string;
};
@@ -1,3 +1,4 @@
import { WORKFLOW_DIAGRAM_NODE_DEFAULT_TARGET_HANDLE_ID } from '@/workflow/workflow-diagram/workflow-nodes/constants/WorkflowDiagramNodeDefaultTargetHandleId';
import styled from '@emotion/styled';
import { Handle, Position } from '@xyflow/react';
@@ -27,6 +28,7 @@ export const WorkflowDiagramHandleTarget = ({
}: WorkflowDiagramHandleTargetProps) => {
return (
<StyledHandle
id={WORKFLOW_DIAGRAM_NODE_DEFAULT_TARGET_HANDLE_ID}
type="target"
position={Position.Top}
isConnectableEnd={isConnectable}
@@ -10,6 +10,7 @@ import { WorkflowNodeLabel } from '@/workflow/workflow-diagram/workflow-nodes/co
import { WorkflowNodeLabelWithCounterPart } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeLabelWithCounterPart';
import { WorkflowNodeRightPart } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeRightPart';
import { WorkflowNodeTitle } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeTitle';
import { WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID } from '@/workflow/workflow-diagram/workflow-nodes/constants/WorkflowDiagramNodeDefaultSourceHandleId';
import { useConnectionState } from '@/workflow/workflow-diagram/workflow-nodes/hooks/useConnectionState';
import { useLingui } from '@lingui/react/macro';
import { Position } from '@xyflow/react';
@@ -66,16 +67,25 @@ export const WorkflowDiagramIteratorEmptyActionEditable = ({
</WorkflowNodeRightPart>
<WorkflowDiagramHandleSource
id={WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID}
type="source"
position={Position.Bottom}
disableHoverEffect
selected={
isSourceSelected(id) ||
isSourceSelected({
nodeId: id,
sourceHandle: WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID,
}) ||
selected ||
isSourceConnected(id) ||
(isConnectable(id) && isHovered)
}
hovered={isSourceHovered(id) || isHovered}
hovered={
isSourceHovered({
nodeId: id,
sourceHandle: WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID,
}) || isHovered
}
/>
</WorkflowNodeContainer>
);
@@ -13,6 +13,7 @@ import { WorkflowNodeLabel } from '@/workflow/workflow-diagram/workflow-nodes/co
import { WorkflowNodeLabelWithCounterPart } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeLabelWithCounterPart';
import { WorkflowNodeRightPart } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeRightPart';
import { WorkflowNodeTitle } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeTitle';
import { WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID } from '@/workflow/workflow-diagram/workflow-nodes/constants/WorkflowDiagramNodeDefaultSourceHandleId';
import { useConnectionState } from '@/workflow/workflow-diagram/workflow-nodes/hooks/useConnectionState';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
@@ -143,15 +144,24 @@ export const WorkflowDiagramStepNodeEditableContent = ({
)}
<WorkflowDiagramHandleSource
id={WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID}
type="source"
position={Position.Bottom}
selected={
isSourceSelected(id) ||
isSourceSelected({
nodeId: id,
sourceHandle: WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID,
}) ||
selected ||
isSourceConnected(id) ||
(isConnectable(id) && isHovered)
}
hovered={isSourceHovered(id) || isHovered}
hovered={
isSourceHovered({
nodeId: id,
sourceHandle: WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID,
}) || isHovered
}
/>
{isDefined(data.rightHandleOptions) && (
@@ -160,13 +170,20 @@ export const WorkflowDiagramStepNodeEditableContent = ({
type="source"
position={Position.Right}
selected={
isSourceSelected(id) ||
isSourceSelected({
nodeId: id,
sourceHandle: data.rightHandleOptions.id,
}) ||
selected ||
isSourceConnected(id) ||
(isConnectable(id) && isHovered)
}
// TODO: fix hovered state when multiple source handles
hovered={isSourceHovered(id) || isHovered}
hovered={
isSourceHovered({
nodeId: id,
sourceHandle: data.rightHandleOptions.id,
}) || isHovered
}
/>
)}
</>
@@ -18,6 +18,7 @@ import { WorkflowNodeLabel } from '@/workflow/workflow-diagram/workflow-nodes/co
import { WorkflowNodeLabelWithCounterPart } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeLabelWithCounterPart';
import { WorkflowNodeRightPart } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeRightPart';
import { WorkflowNodeTitle } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeTitle';
import { WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID } from '@/workflow/workflow-diagram/workflow-nodes/constants/WorkflowDiagramNodeDefaultSourceHandleId';
import { Position } from '@xyflow/react';
import { useContext } from 'react';
import { useSetRecoilState } from 'recoil';
@@ -97,6 +98,7 @@ export const WorkflowDiagramStepNodeReadonly = ({
</WorkflowNodeContainer>
<WorkflowDiagramHandleSource
id={WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID}
type="source"
selected={selected}
position={Position.Bottom}
@@ -18,6 +18,7 @@ import { WorkflowNodeLabel } from '@/workflow/workflow-diagram/workflow-nodes/co
import { WorkflowNodeLabelWithCounterPart } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeLabelWithCounterPart';
import { WorkflowNodeRightPart } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeRightPart';
import { WorkflowNodeTitle } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeTitle';
import { WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID } from '@/workflow/workflow-diagram/workflow-nodes/constants/WorkflowDiagramNodeDefaultSourceHandleId';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { Position } from '@xyflow/react';
@@ -152,6 +153,7 @@ export const WorkflowRunDiagramStepNode = ({
</WorkflowNodeContainer>
<WorkflowDiagramHandleSource
id={WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID}
type="source"
position={Position.Bottom}
disableHoverEffect
@@ -0,0 +1 @@
export const WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID = 'default';
@@ -0,0 +1 @@
export const WORKFLOW_DIAGRAM_NODE_DEFAULT_TARGET_HANDLE_ID = 'default';
@@ -1,5 +1,5 @@
import { useGetUpdatableWorkflowVersionOrThrow } from '@/workflow/hooks/useGetUpdatableWorkflowVersionOrThrow';
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdge';
import { type WorkflowDiagramEdgeDescriptor } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdgeDescriptor';
import { useCreateWorkflowVersionEdge } from '@/workflow/workflow-steps/hooks/useCreateWorkflowVersionEdge';
import { useState } from 'react';
@@ -11,7 +11,10 @@ export const useCreateEdge = () => {
const { getUpdatableWorkflowVersion } =
useGetUpdatableWorkflowVersionOrThrow();
const createEdge = async ({ source, target }: WorkflowDiagramEdge) => {
const createEdge = async ({
source,
target,
}: WorkflowDiagramEdgeDescriptor) => {
if (isLoading) {
return;
}
@@ -1,5 +1,5 @@
import { useGetUpdatableWorkflowVersionOrThrow } from '@/workflow/hooks/useGetUpdatableWorkflowVersionOrThrow';
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdge';
import { type WorkflowDiagramEdgeDescriptor } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdgeDescriptor';
import { useDeleteWorkflowVersionEdge } from '@/workflow/workflow-steps/hooks/useDeleteWorkflowVersionEdge';
import { useState } from 'react';
@@ -11,7 +11,10 @@ export const useDeleteEdge = () => {
const { getUpdatableWorkflowVersion } =
useGetUpdatableWorkflowVersionOrThrow();
const deleteEdge = async ({ source, target }: WorkflowDiagramEdge) => {
const deleteEdge = async ({
source,
target,
}: WorkflowDiagramEdgeDescriptor) => {
if (isLoading) {
return;
}