Fix connection design (#14070)
- refactor edges related files into sub-module - remove top handle from workflow, workflowVersion and workflowRun - add a custom design for connection (edge being created) - connect when hovering a connectable node - fix marker z-index - refactor Handles ## Before https://github.com/user-attachments/assets/013dec36-5cc7-4561-938c-404511118578 <img width="934" height="409" alt="image" src="https://github.com/user-attachments/assets/2b43c6cb-0bf3-4110-b1cc-b2e50c15c9a8" /> ## After https://github.com/user-attachments/assets/a5b32cbf-bfbd-48e6-9889-037234adf38f <img width="1433" height="599" alt="image" src="https://github.com/user-attachments/assets/02edc085-9b23-4434-b666-bf25e5c430a8" />
This commit is contained in:
+1
-1
@@ -10,7 +10,7 @@ import { PAGE_HEADER_COMMAND_MENU_BUTTON_CLICK_OUTSIDE_ID } from '@/ui/layout/pa
|
||||
import { currentFocusIdSelector } from '@/ui/utilities/focus/states/currentFocusIdSelector';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { WORKFLOW_DIAGRAM_CREATE_STEP_NODE_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramCreateStepNodeClickOutsideId';
|
||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/workflow-edges/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||
import { WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramStepNodeClickOutsideId';
|
||||
import { useTheme } from '@emotion/react';
|
||||
|
||||
|
||||
+5
-2
@@ -5,7 +5,7 @@ import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { WorkflowDiagramCustomMarkers } from '@/workflow/workflow-diagram/components/WorkflowDiagramCustomMarkers';
|
||||
import { WorkflowDiagramCustomMarkers } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramCustomMarkers';
|
||||
import { WorkflowDiagramRightClickCommandMenu } from '@/workflow/workflow-diagram/components/WorkflowDiagramRightClickCommandMenu';
|
||||
import { useRightDrawerState } from '@/workflow/workflow-diagram/hooks/useRightDrawerState';
|
||||
import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState';
|
||||
@@ -54,7 +54,8 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { Tag, type TagColor } from 'twenty-ui/components';
|
||||
import { THEME_COMMON } from 'twenty-ui/theme';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { useEdgeState } from '@/workflow/workflow-diagram/hooks/useEdgeState';
|
||||
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
|
||||
import { WorkflowDiagramConnection } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramConnection';
|
||||
|
||||
const StyledResetReactflowStyles = styled.div`
|
||||
height: 100%;
|
||||
@@ -496,6 +497,8 @@ export const WorkflowDiagramCanvasBase = ({
|
||||
nodesConnectable={isWorkflowBranchEnabled ? nodesConnectable : false}
|
||||
paneClickDistance={10} // Fix small unwanted user dragging does not select node
|
||||
preventScrolling={false}
|
||||
connectionLineComponent={WorkflowDiagramConnection}
|
||||
connectionRadius={0}
|
||||
>
|
||||
<Background color={theme.border.color.medium} size={2} />
|
||||
|
||||
|
||||
+3
-4
@@ -2,11 +2,10 @@ import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/ho
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
|
||||
import { WorkflowDiagramBlankEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramBlankEdge';
|
||||
import { WorkflowDiagramBlankEdge } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramBlankEdge';
|
||||
import { WorkflowDiagramCanvasBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase';
|
||||
import { WorkflowDiagramCanvasEditableEffect } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasEditableEffect';
|
||||
import { WorkflowDiagramDefaultEdgeEditable } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdgeEditable';
|
||||
|
||||
import { WorkflowDiagramDefaultEdgeEditable } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramDefaultEdgeEditable';
|
||||
import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState';
|
||||
import { workflowDiagramRightClickMenuPositionState } from '@/workflow/workflow-diagram/states/workflowDiagramRightClickMenuPositionState';
|
||||
import {
|
||||
@@ -15,7 +14,7 @@ import {
|
||||
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||
import { getWorkflowVersionStatusTagProps } from '@/workflow/workflow-diagram/utils/getWorkflowVersionStatusTagProps';
|
||||
import { WorkflowDiagramEmptyTriggerEditable } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramEmptyTriggerEditable';
|
||||
import { WorkflowDiagramFilterEdgeEditable } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramFilterEdgeEditable';
|
||||
import { WorkflowDiagramFilterEdgeEditable } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramFilterEdgeEditable';
|
||||
import { WorkflowDiagramStepNodeEditable } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeEditable';
|
||||
import { useCreateEdge } from '@/workflow/workflow-steps/hooks/useCreateEdge';
|
||||
import { useDeleteEdge } from '@/workflow/workflow-steps/hooks/useDeleteEdge';
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { useEdgeState } from '@/workflow/workflow-diagram/hooks/useEdgeState';
|
||||
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import {
|
||||
type OnSelectionChangeParams,
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import { type WorkflowVersionStatus } from '@/workflow/types/Workflow';
|
||||
import { WorkflowDiagramCanvasBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase';
|
||||
import { WorkflowDiagramDefaultEdgeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdgeReadonly';
|
||||
import { WorkflowDiagramDefaultEdgeReadonly } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramDefaultEdgeReadonly';
|
||||
|
||||
import { getWorkflowVersionStatusTagProps } from '@/workflow/workflow-diagram/utils/getWorkflowVersionStatusTagProps';
|
||||
import { WorkflowDiagramEmptyTriggerReadonly } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramEmptyTriggerReadonly';
|
||||
import { WorkflowDiagramFilterEdgeReadonly } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramFilterEdgeReadonly';
|
||||
import { WorkflowDiagramFilterEdgeReadonly } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramFilterEdgeReadonly';
|
||||
import { WorkflowDiagramStepNodeReadonly } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeReadonly';
|
||||
import { ReactFlowProvider } from '@xyflow/react';
|
||||
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { type WorkflowRunStatus } from '@/workflow/types/Workflow';
|
||||
import { WorkflowDiagramCanvasBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase';
|
||||
import { WorkflowDiagramDefaultEdgeRun } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdgeRun';
|
||||
import { WorkflowDiagramFilterEdgeRun } from '@/workflow/workflow-diagram/components/WorkflowDiagramFilterEdgeRun';
|
||||
import { WorkflowDiagramDefaultEdgeRun } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramDefaultEdgeRun';
|
||||
import { WorkflowDiagramFilterEdgeRun } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramFilterEdgeRun';
|
||||
|
||||
import { getWorkflowRunStatusTagProps } from '@/workflow/workflow-diagram/utils/getWorkflowRunStatusTagProps';
|
||||
import { WorkflowRunDiagramStepNode } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowRunDiagramStepNode';
|
||||
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
export enum EdgeBranchArrowMarker {
|
||||
Default = 'edge-branch-arrow-default',
|
||||
Hover = 'edge-branch-arrow-hover',
|
||||
Selected = 'edge-branch-arrow-selected',
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
import { workflowSelectedEdgeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedEdgeComponentState';
|
||||
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
|
||||
import { type WorkflowDiagramNodeHandles } from '@/workflow/workflow-diagram/types/WorkflowDiagramNodeHandles';
|
||||
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagramEdge';
|
||||
import { workflowHoveredEdgeComponentState } from '@/workflow/workflow-diagram/states/workflowHoveredEdgeComponentState';
|
||||
import { useReactFlow } from '@xyflow/react';
|
||||
import { EdgeBranchArrowMarker } from '@/workflow/workflow-diagram/constants/EdgeBranchArrowMarker';
|
||||
|
||||
export const useEdgeState = () => {
|
||||
const reactflow = useReactFlow();
|
||||
|
||||
const [workflowSelectedEdge, setWorkflowSelectedEdge] =
|
||||
useRecoilComponentState(workflowSelectedEdgeComponentState);
|
||||
|
||||
const [workflowHoveredEdge, setWorkflowHoveredEdge] = useRecoilComponentState(
|
||||
workflowHoveredEdgeComponentState,
|
||||
);
|
||||
|
||||
const isEdgeSelected = ({ source, target }: WorkflowDiagramEdge) => {
|
||||
return (
|
||||
workflowSelectedEdge?.source === source &&
|
||||
workflowSelectedEdge?.target === target
|
||||
);
|
||||
};
|
||||
|
||||
const isEdgeHovered = ({ source, target }: WorkflowDiagramEdge) => {
|
||||
return (
|
||||
workflowHoveredEdge?.source === source &&
|
||||
workflowHoveredEdge?.target === target
|
||||
);
|
||||
};
|
||||
|
||||
const getNodeHandlesSelectedState = (
|
||||
id: string,
|
||||
): WorkflowDiagramNodeHandles => {
|
||||
return {
|
||||
targetHandle: workflowSelectedEdge?.target === id,
|
||||
sourceHandle: workflowSelectedEdge?.source === id,
|
||||
};
|
||||
};
|
||||
|
||||
const getNodeHandlesHoveredState = (
|
||||
id: string,
|
||||
): WorkflowDiagramNodeHandles => {
|
||||
return {
|
||||
targetHandle: workflowHoveredEdge?.target === id,
|
||||
sourceHandle: workflowHoveredEdge?.source === id,
|
||||
};
|
||||
};
|
||||
|
||||
const setEdgeSelected = ({ source, target }: WorkflowDiagramEdge) => {
|
||||
if (
|
||||
workflowSelectedEdge?.source === source &&
|
||||
workflowSelectedEdge?.target === target
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
setWorkflowSelectedEdge({ source, target });
|
||||
|
||||
reactflow.setEdges((edges) =>
|
||||
edges.map((edge) => ({
|
||||
...edge,
|
||||
markerEnd:
|
||||
edge.source === source && edge.target === target
|
||||
? EdgeBranchArrowMarker.Selected
|
||||
: EdgeBranchArrowMarker.Default,
|
||||
})),
|
||||
);
|
||||
};
|
||||
|
||||
const setEdgeHovered = ({ source, target }: WorkflowDiagramEdge) => {
|
||||
if (
|
||||
workflowHoveredEdge?.source === source &&
|
||||
workflowHoveredEdge?.target === target
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
setWorkflowHoveredEdge({ source, target });
|
||||
|
||||
reactflow.setEdges((edges) =>
|
||||
edges.map((edge) =>
|
||||
edge.source === source &&
|
||||
edge.target === target &&
|
||||
edge.markerEnd !== EdgeBranchArrowMarker.Selected
|
||||
? {
|
||||
...edge,
|
||||
markerEnd: EdgeBranchArrowMarker.Hover,
|
||||
}
|
||||
: edge,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const clearEdgeSelected = () => {
|
||||
setWorkflowSelectedEdge(undefined);
|
||||
|
||||
reactflow.setEdges((edges) =>
|
||||
edges.map((edge) => ({
|
||||
...edge,
|
||||
markerEnd: EdgeBranchArrowMarker.Default,
|
||||
})),
|
||||
);
|
||||
};
|
||||
|
||||
const clearEdgeHover = () => {
|
||||
setWorkflowHoveredEdge(undefined);
|
||||
|
||||
reactflow.setEdges((edges) =>
|
||||
edges.map((edge) =>
|
||||
edge.markerEnd === EdgeBranchArrowMarker.Hover
|
||||
? {
|
||||
...edge,
|
||||
markerEnd: EdgeBranchArrowMarker.Default,
|
||||
}
|
||||
: edge,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
isEdgeSelected,
|
||||
setEdgeSelected,
|
||||
clearEdgeSelected,
|
||||
getNodeHandlesSelectedState,
|
||||
isEdgeHovered,
|
||||
setEdgeHovered,
|
||||
clearEdgeHover,
|
||||
getNodeHandlesHoveredState,
|
||||
};
|
||||
};
|
||||
+14
@@ -125,6 +125,7 @@ describe('generateWorkflowRunDiagram', () => {
|
||||
"source": "trigger",
|
||||
"target": "step1",
|
||||
"type": "empty-filter--run",
|
||||
"zIndex": -2,
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
@@ -139,6 +140,7 @@ describe('generateWorkflowRunDiagram', () => {
|
||||
"source": "step1",
|
||||
"target": "step2",
|
||||
"type": "empty-filter--run",
|
||||
"zIndex": -2,
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
@@ -153,6 +155,7 @@ describe('generateWorkflowRunDiagram', () => {
|
||||
"source": "step2",
|
||||
"target": "step3",
|
||||
"type": "empty-filter--run",
|
||||
"zIndex": -2,
|
||||
},
|
||||
],
|
||||
"nodes": [
|
||||
@@ -355,6 +358,7 @@ describe('generateWorkflowRunDiagram', () => {
|
||||
"source": "trigger",
|
||||
"target": "step1",
|
||||
"type": "empty-filter--run",
|
||||
"zIndex": -2,
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
@@ -369,6 +373,7 @@ describe('generateWorkflowRunDiagram', () => {
|
||||
"source": "step1",
|
||||
"target": "step2",
|
||||
"type": "empty-filter--run",
|
||||
"zIndex": -2,
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
@@ -383,6 +388,7 @@ describe('generateWorkflowRunDiagram', () => {
|
||||
"source": "step2",
|
||||
"target": "step3",
|
||||
"type": "empty-filter--run",
|
||||
"zIndex": -2,
|
||||
},
|
||||
],
|
||||
"nodes": [
|
||||
@@ -585,6 +591,7 @@ describe('generateWorkflowRunDiagram', () => {
|
||||
"source": "trigger",
|
||||
"target": "step1",
|
||||
"type": "empty-filter--run",
|
||||
"zIndex": -2,
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
@@ -599,6 +606,7 @@ describe('generateWorkflowRunDiagram', () => {
|
||||
"source": "step1",
|
||||
"target": "step2",
|
||||
"type": "empty-filter--run",
|
||||
"zIndex": -2,
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
@@ -613,6 +621,7 @@ describe('generateWorkflowRunDiagram', () => {
|
||||
"source": "step2",
|
||||
"target": "step3",
|
||||
"type": "empty-filter--run",
|
||||
"zIndex": -2,
|
||||
},
|
||||
],
|
||||
"nodes": [
|
||||
@@ -834,6 +843,7 @@ describe('generateWorkflowRunDiagram', () => {
|
||||
"source": "trigger",
|
||||
"target": "step1",
|
||||
"type": "empty-filter--run",
|
||||
"zIndex": -2,
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
@@ -848,6 +858,7 @@ describe('generateWorkflowRunDiagram', () => {
|
||||
"source": "step1",
|
||||
"target": "step2",
|
||||
"type": "empty-filter--run",
|
||||
"zIndex": -2,
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
@@ -862,6 +873,7 @@ describe('generateWorkflowRunDiagram', () => {
|
||||
"source": "step2",
|
||||
"target": "step3",
|
||||
"type": "empty-filter--run",
|
||||
"zIndex": -2,
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
@@ -876,6 +888,7 @@ describe('generateWorkflowRunDiagram', () => {
|
||||
"source": "step3",
|
||||
"target": "step4",
|
||||
"type": "empty-filter--run",
|
||||
"zIndex": -2,
|
||||
},
|
||||
],
|
||||
"nodes": [
|
||||
@@ -1063,6 +1076,7 @@ describe('generateWorkflowRunDiagram', () => {
|
||||
"source": "trigger",
|
||||
"target": "step1",
|
||||
"type": "empty-filter--run",
|
||||
"zIndex": -2,
|
||||
},
|
||||
],
|
||||
"nodes": [
|
||||
|
||||
+1
@@ -163,6 +163,7 @@ describe('getWorkflowVersionDiagram', () => {
|
||||
"source": "trigger",
|
||||
"target": "step-1",
|
||||
"type": "empty-filter--editable",
|
||||
"zIndex": -2,
|
||||
},
|
||||
],
|
||||
"nodes": [
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import {
|
||||
} from '@/workflow/types/Workflow';
|
||||
import { FIRST_NODE_POSITION } from '@/workflow/workflow-diagram/constants/FirstNodePosition';
|
||||
import { VERTICAL_DISTANCE_BETWEEN_TWO_NODES } from '@/workflow/workflow-diagram/constants/VerticalDistanceBetweenTwoNodes';
|
||||
import { WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION } from '@/workflow/workflow-diagram/constants/WorkflowVisualizerEdgeDefaultConfiguration';
|
||||
import { WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION } from '@/workflow/workflow-diagram/workflow-edges/constants/WorkflowVisualizerEdgeDefaultConfiguration';
|
||||
import {
|
||||
type WorkflowDiagram,
|
||||
type WorkflowDiagramEdge,
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
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/hooks/useEdgeState';
|
||||
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
|
||||
|
||||
type WorkflowDiagramBaseEdgeProps = Pick<
|
||||
EdgeProps<WorkflowDiagramEdge>,
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
type ConnectionLineComponentProps,
|
||||
getBezierPath,
|
||||
} from '@xyflow/react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { EDGE_BRANCH_ARROW_MARKER } from '@/workflow/workflow-diagram/workflow-edges/constants/EdgeBranchArrowMarker';
|
||||
|
||||
type WorkflowDiagramConnectionProps = ConnectionLineComponentProps;
|
||||
|
||||
export const WorkflowDiagramConnection = ({
|
||||
fromX,
|
||||
fromY,
|
||||
toX,
|
||||
toY,
|
||||
}: WorkflowDiagramConnectionProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [path] = getBezierPath({
|
||||
sourceX: fromX,
|
||||
sourceY: fromY + 4,
|
||||
targetX: toX,
|
||||
targetY: toY - 4,
|
||||
});
|
||||
|
||||
return (
|
||||
<path
|
||||
d={path}
|
||||
fill="none"
|
||||
stroke={theme.color.blue}
|
||||
markerEnd={`url(#${EDGE_BRANCH_ARROW_MARKER.Selected.markerEnd})`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
import { EdgeBranchArrowMarker } from '@/workflow/workflow-diagram/constants/EdgeBranchArrowMarker';
|
||||
import { EDGE_BRANCH_ARROW_MARKER } from '@/workflow/workflow-diagram/workflow-edges/constants/EdgeBranchArrowMarker';
|
||||
import { useTheme } from '@emotion/react';
|
||||
|
||||
export const WorkflowDiagramCustomMarkers = () => {
|
||||
@@ -8,7 +8,7 @@ export const WorkflowDiagramCustomMarkers = () => {
|
||||
<svg style={{ position: 'absolute', top: 0, left: 0 }}>
|
||||
<defs>
|
||||
<marker
|
||||
id={EdgeBranchArrowMarker.Default}
|
||||
id={EDGE_BRANCH_ARROW_MARKER.Default.markerEnd}
|
||||
markerHeight={8}
|
||||
markerWidth={10}
|
||||
refX={5}
|
||||
@@ -22,7 +22,7 @@ export const WorkflowDiagramCustomMarkers = () => {
|
||||
</marker>
|
||||
|
||||
<marker
|
||||
id={EdgeBranchArrowMarker.Hover}
|
||||
id={EDGE_BRANCH_ARROW_MARKER.Hover.markerEnd}
|
||||
markerHeight={8}
|
||||
markerWidth={10}
|
||||
refX={5}
|
||||
@@ -36,7 +36,7 @@ export const WorkflowDiagramCustomMarkers = () => {
|
||||
</marker>
|
||||
|
||||
<marker
|
||||
id={EdgeBranchArrowMarker.Selected}
|
||||
id={EDGE_BRANCH_ARROW_MARKER.Selected.markerEnd}
|
||||
markerHeight={8}
|
||||
markerWidth={10}
|
||||
refX={5}
|
||||
+6
-6
@@ -3,11 +3,11 @@ import { commandMenuNavigationStackState } from '@/command-menu/states/commandMe
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
|
||||
import { WorkflowDiagramBaseEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramBaseEdge';
|
||||
import { WorkflowDiagramEdgeButtonGroup } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeButtonGroup';
|
||||
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2Container';
|
||||
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2VisibilityContainer';
|
||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||
import { WorkflowDiagramBaseEdge } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramBaseEdge';
|
||||
import { WorkflowDiagramEdgeButtonGroup } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeButtonGroup';
|
||||
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 { useOpenWorkflowEditFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowEditFilterInCommandMenu';
|
||||
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
|
||||
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||
@@ -24,7 +24,7 @@ import { useSetRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconFilter, IconPlus, IconTrash } from 'twenty-ui/display';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { useEdgeState } from '@/workflow/workflow-diagram/hooks/useEdgeState';
|
||||
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
|
||||
|
||||
type WorkflowDiagramDefaultEdgeEditableProps = EdgeProps<WorkflowDiagramEdge>;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { WorkflowRunDiagramBaseEdge } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramBaseEdge';
|
||||
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';
|
||||
|
||||
+6
-6
@@ -9,11 +9,12 @@ import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import { WorkflowDiagramBaseEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramBaseEdge';
|
||||
import { WorkflowDiagramEdgeButtonGroup } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeButtonGroup';
|
||||
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2Container';
|
||||
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2VisibilityContainer';
|
||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||
import { WorkflowDiagramBaseEdge } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramBaseEdge';
|
||||
import { WorkflowDiagramEdgeButtonGroup } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeButtonGroup';
|
||||
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2Container';
|
||||
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2VisibilityContainer';
|
||||
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
|
||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/workflow-edges/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||
import { useOpenWorkflowEditFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowEditFilterInCommandMenu';
|
||||
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
|
||||
import { workflowDiagramPanOnDragComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramPanOnDragComponentState';
|
||||
@@ -47,7 +48,6 @@ import {
|
||||
import { IconButtonGroup } from 'twenty-ui/input';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { useEdgeState } from '@/workflow/workflow-diagram/hooks/useEdgeState';
|
||||
|
||||
type WorkflowDiagramFilterEdgeEditableProps = EdgeProps<WorkflowDiagramEdge>;
|
||||
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
|
||||
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2Container';
|
||||
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2VisibilityContainer';
|
||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||
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 { useOpenWorkflowViewFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowViewFilterInCommandMenu';
|
||||
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
|
||||
import {
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2Container';
|
||||
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2VisibilityContainer';
|
||||
import { WorkflowRunDiagramBaseEdge } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramBaseEdge';
|
||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||
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 { useOpenWorkflowRunFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowRunFilterInCommandMenu';
|
||||
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
|
||||
import {
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
enum EdgeBranchArrowState {
|
||||
Default = 'Default',
|
||||
Hover = 'Hover',
|
||||
Selected = 'Selected',
|
||||
}
|
||||
|
||||
export const EDGE_BRANCH_ARROW_MARKER: Record<
|
||||
EdgeBranchArrowState,
|
||||
{ markerEnd: string; zIndex: number }
|
||||
> = {
|
||||
[EdgeBranchArrowState.Default]: {
|
||||
markerEnd: 'edge-branch-arrow-default',
|
||||
zIndex: -2,
|
||||
},
|
||||
[EdgeBranchArrowState.Hover]: {
|
||||
markerEnd: 'edge-branch-arrow-hover',
|
||||
zIndex: -1,
|
||||
},
|
||||
[EdgeBranchArrowState.Selected]: {
|
||||
markerEnd: 'edge-branch-arrow-selected',
|
||||
zIndex: 0,
|
||||
},
|
||||
};
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import { EdgeBranchArrowMarker } from '@/workflow/workflow-diagram/constants/EdgeBranchArrowMarker';
|
||||
import { EDGE_BRANCH_ARROW_MARKER } from '@/workflow/workflow-diagram/workflow-edges/constants/EdgeBranchArrowMarker';
|
||||
import {
|
||||
type WorkflowDiagramEdge,
|
||||
type WorkflowDiagramEdgeType,
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
export const WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION = {
|
||||
type: 'empty-filter--readonly' satisfies WorkflowDiagramEdgeType,
|
||||
markerStart: undefined,
|
||||
markerEnd: EdgeBranchArrowMarker.Default,
|
||||
...EDGE_BRANCH_ARROW_MARKER.Default,
|
||||
deletable: false,
|
||||
selectable: false,
|
||||
data: {
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
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';
|
||||
|
||||
export const useEdgeState = () => {
|
||||
const reactflow = useReactFlow();
|
||||
|
||||
const [workflowSelectedEdge, setWorkflowSelectedEdge] =
|
||||
useRecoilComponentState(workflowSelectedEdgeComponentState);
|
||||
|
||||
const [workflowHoveredEdge, setWorkflowHoveredEdge] = useRecoilComponentState(
|
||||
workflowHoveredEdgeComponentState,
|
||||
);
|
||||
|
||||
const isSourceSelected = (nodeId: string) => {
|
||||
return workflowSelectedEdge?.source === nodeId;
|
||||
};
|
||||
|
||||
const isSourceHovered = (nodeId: string) => {
|
||||
return workflowHoveredEdge?.source === nodeId;
|
||||
};
|
||||
|
||||
const isEdgeSelected = ({ source, target }: WorkflowDiagramEdge) => {
|
||||
return isSourceSelected(source) && workflowSelectedEdge?.target === target;
|
||||
};
|
||||
|
||||
const isEdgeHovered = ({ source, target }: WorkflowDiagramEdge) => {
|
||||
return isSourceHovered(source) && workflowHoveredEdge?.target === target;
|
||||
};
|
||||
|
||||
const setEdgeSelected = ({ source, target }: WorkflowDiagramEdge) => {
|
||||
if (isEdgeSelected({ source, target })) {
|
||||
return;
|
||||
}
|
||||
|
||||
setWorkflowSelectedEdge({ source, target });
|
||||
|
||||
reactflow.setEdges((edges) =>
|
||||
edges.map((edge) => {
|
||||
if (edge.source === source && edge.target === target) {
|
||||
return {
|
||||
...edge,
|
||||
...EDGE_BRANCH_ARROW_MARKER.Selected,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...edge,
|
||||
...EDGE_BRANCH_ARROW_MARKER.Default,
|
||||
};
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setEdgeHovered = ({ source, target }: WorkflowDiagramEdge) => {
|
||||
setWorkflowHoveredEdge({ source, target });
|
||||
|
||||
reactflow.setEdges((edges) =>
|
||||
edges.map((edge) => {
|
||||
if (
|
||||
edge.source === source &&
|
||||
edge.target === target &&
|
||||
edge.markerEnd !== EDGE_BRANCH_ARROW_MARKER.Selected.markerEnd
|
||||
) {
|
||||
return {
|
||||
...edge,
|
||||
...EDGE_BRANCH_ARROW_MARKER.Hover,
|
||||
};
|
||||
}
|
||||
|
||||
return edge;
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const clearEdgeSelected = () => {
|
||||
setWorkflowSelectedEdge(undefined);
|
||||
|
||||
reactflow.setEdges((edges) =>
|
||||
edges.map((edge) => ({
|
||||
...edge,
|
||||
...EDGE_BRANCH_ARROW_MARKER.Default,
|
||||
})),
|
||||
);
|
||||
};
|
||||
|
||||
const clearEdgeHover = () => {
|
||||
setWorkflowHoveredEdge(undefined);
|
||||
|
||||
reactflow.setEdges((edges) =>
|
||||
edges.map((edge) => {
|
||||
if (edge.markerEnd === EDGE_BRANCH_ARROW_MARKER.Hover.markerEnd) {
|
||||
return {
|
||||
...edge,
|
||||
...EDGE_BRANCH_ARROW_MARKER.Default,
|
||||
};
|
||||
}
|
||||
|
||||
return edge;
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
isEdgeSelected,
|
||||
isSourceSelected,
|
||||
setEdgeSelected,
|
||||
clearEdgeSelected,
|
||||
isEdgeHovered,
|
||||
isSourceHovered,
|
||||
setEdgeHovered,
|
||||
clearEdgeHover,
|
||||
};
|
||||
};
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
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/types/WorkflowDiagramEdge';
|
||||
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdge';
|
||||
|
||||
export const workflowHoveredEdgeComponentState = createComponentState<
|
||||
WorkflowDiagramEdge | undefined
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
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/types/WorkflowDiagramEdge';
|
||||
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdge';
|
||||
|
||||
export const workflowSelectedEdgeComponentState = createComponentState<
|
||||
WorkflowDiagramEdge | undefined
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
import { NODE_HANDLE_HEIGHT_PX } from '@/workflow/workflow-diagram/constants/NodeHandleHeightPx';
|
||||
import { NODE_HANDLE_WIDTH_PX } from '@/workflow/workflow-diagram/constants/NodeHandleWidthPx';
|
||||
import { css } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Handle, type HandleProps } from '@xyflow/react';
|
||||
|
||||
type WorkflowDiagramHandleReadonlyProps = HandleProps & {
|
||||
selected: boolean;
|
||||
};
|
||||
|
||||
const StyledHandle = styled(Handle)<{
|
||||
selected: boolean;
|
||||
}>`
|
||||
&.react-flow__handle {
|
||||
height: ${NODE_HANDLE_HEIGHT_PX}px;
|
||||
width: ${NODE_HANDLE_WIDTH_PX}px;
|
||||
border-color: ${({ theme, selected }) =>
|
||||
selected ? theme.color.blue : theme.border.color.strong};
|
||||
background: ${({ theme, selected }) =>
|
||||
selected ? theme.adaptiveColors.blue1 : theme.background.primary};
|
||||
transition:
|
||||
transform 0.1s ease-out,
|
||||
background 0.1s,
|
||||
border-color 0.1s;
|
||||
z-index: 1;
|
||||
|
||||
${({ position }) => {
|
||||
if (position === 'top') {
|
||||
return css`
|
||||
transform: translate(-50%, -50%);
|
||||
transform-origin: top left;
|
||||
`;
|
||||
} else if (position === 'bottom') {
|
||||
return css`
|
||||
transform: translate(-50%, 50%);
|
||||
transform-origin: bottom left;
|
||||
`;
|
||||
}
|
||||
}}
|
||||
|
||||
&.connectionindicator {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const WorkflowDiagramHandleReadonly = ({
|
||||
type,
|
||||
position,
|
||||
selected,
|
||||
}: WorkflowDiagramHandleReadonlyProps) => {
|
||||
return <StyledHandle type={type} position={position} selected={selected} />;
|
||||
};
|
||||
+17
-35
@@ -3,12 +3,13 @@ import { NODE_HANDLE_WIDTH_PX } from '@/workflow/workflow-diagram/constants/Node
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { css } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Handle, type HandleProps } from '@xyflow/react';
|
||||
import { Handle, Position, type HandleProps } from '@xyflow/react';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
type WorkflowDiagramHandleEditableProps = HandleProps & {
|
||||
type WorkflowDiagramHandleSourceProps = {
|
||||
selected: boolean;
|
||||
hovered?: boolean;
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
const HANDLE_SCALE_ON_HOVER = 1.5;
|
||||
@@ -17,17 +18,19 @@ const StyledHandle = styled(Handle, {
|
||||
shouldForwardProp: (prop) =>
|
||||
prop !== 'disableHoverEffect' && prop !== 'selected' && prop !== 'hovered',
|
||||
})<{
|
||||
type: HandleProps['type'];
|
||||
disableHoverEffect: boolean;
|
||||
selected: boolean;
|
||||
hovered?: boolean;
|
||||
}>`
|
||||
&.react-flow__handle {
|
||||
opacity: ${({ type }) => (type === 'target' ? 0 : 1)};
|
||||
height: ${NODE_HANDLE_HEIGHT_PX}px;
|
||||
width: ${NODE_HANDLE_WIDTH_PX}px;
|
||||
border-color: ${({ theme, selected, hovered }) =>
|
||||
border-color: ${({ theme, selected, hovered, disableHoverEffect }) =>
|
||||
selected
|
||||
? theme.color.blue
|
||||
: hovered
|
||||
: hovered && !disableHoverEffect
|
||||
? theme.font.color.light
|
||||
: theme.border.color.strong};
|
||||
background: ${({ theme, selected }) =>
|
||||
@@ -38,25 +41,14 @@ const StyledHandle = styled(Handle, {
|
||||
border-color 0.1s;
|
||||
z-index: 1;
|
||||
|
||||
${({ position }) => {
|
||||
if (position === 'top') {
|
||||
return css`
|
||||
transform: translate(-50%, -50%);
|
||||
transform-origin: top left;
|
||||
`;
|
||||
} else if (position === 'bottom') {
|
||||
return css`
|
||||
transform: translate(-50%, 50%);
|
||||
transform-origin: bottom left;
|
||||
`;
|
||||
}
|
||||
}}
|
||||
transform: translate(-50%, 50%);
|
||||
transform-origin: bottom left;
|
||||
|
||||
&.connectionindicator {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
${({ disableHoverEffect, theme, position }) => {
|
||||
${({ disableHoverEffect, theme }) => {
|
||||
if (disableHoverEffect) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -65,37 +57,27 @@ const StyledHandle = styled(Handle, {
|
||||
&:hover {
|
||||
background: ${theme.adaptiveColors.blue1} !important;
|
||||
border-color: ${theme.color.blue} !important;
|
||||
|
||||
${position === 'top' &&
|
||||
css`
|
||||
transform: scale(${HANDLE_SCALE_ON_HOVER}) translate(-50%, -50%);
|
||||
`}
|
||||
|
||||
${position === 'bottom' &&
|
||||
css`
|
||||
transform: scale(${HANDLE_SCALE_ON_HOVER}) translate(-50%, 50%);
|
||||
`}
|
||||
transform: scale(${HANDLE_SCALE_ON_HOVER}) translate(-50%, 50%);
|
||||
}
|
||||
`;
|
||||
}}
|
||||
}
|
||||
`;
|
||||
|
||||
export const WorkflowDiagramHandleEditable = ({
|
||||
type,
|
||||
position,
|
||||
export const WorkflowDiagramHandleSource = ({
|
||||
selected,
|
||||
hovered = false,
|
||||
}: WorkflowDiagramHandleEditableProps) => {
|
||||
readOnly = false,
|
||||
}: WorkflowDiagramHandleSourceProps) => {
|
||||
const isWorkflowBranchEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED,
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledHandle
|
||||
type={type}
|
||||
position={position}
|
||||
disableHoverEffect={!isWorkflowBranchEnabled}
|
||||
type={'source'}
|
||||
position={Position.Bottom}
|
||||
disableHoverEffect={!isWorkflowBranchEnabled || readOnly}
|
||||
selected={selected}
|
||||
hovered={hovered}
|
||||
/>
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Handle, Position } from '@xyflow/react';
|
||||
|
||||
type WorkflowDiagramHandleTargetProps = {
|
||||
isConnectable?: boolean;
|
||||
};
|
||||
|
||||
const StyledHandle = styled(Handle)`
|
||||
&.react-flow__handle {
|
||||
opacity: 0;
|
||||
z-index: 1;
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
transform: translate(-1px, -5px);
|
||||
|
||||
&.connectionindicator {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const WorkflowDiagramHandleTarget = ({
|
||||
isConnectable = false,
|
||||
}: WorkflowDiagramHandleTargetProps) => {
|
||||
return (
|
||||
<StyledHandle
|
||||
type={'target'}
|
||||
position={Position.Top}
|
||||
isConnectableEnd={isConnectable}
|
||||
isConnectableStart={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+27
-24
@@ -1,10 +1,9 @@
|
||||
import { WorkflowDiagramCreateStepElement } from '@/workflow/workflow-diagram/components/WorkflowDiagramCreateStepElement';
|
||||
import { WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramStepNodeClickOutsideId';
|
||||
import { useEdgeState } from '@/workflow/workflow-diagram/hooks/useEdgeState';
|
||||
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
|
||||
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
|
||||
import { type WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||
import { type WorkflowDiagramNodeVariant } from '@/workflow/workflow-diagram/types/WorkflowDiagramNodeVariant';
|
||||
import { WorkflowDiagramHandleEditable } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramHandleEditable';
|
||||
import { WorkflowDiagramStepNodeIcon } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeIcon';
|
||||
import { WorkflowNodeContainer } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeContainer';
|
||||
import { WorkflowNodeIconContainer } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeIconContainer';
|
||||
@@ -13,11 +12,14 @@ import { WorkflowNodeLabelWithCounterPart } from '@/workflow/workflow-diagram/wo
|
||||
import { WorkflowNodeRightPart } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeRightPart';
|
||||
import { WorkflowNodeTitle } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeTitle';
|
||||
import styled from '@emotion/styled';
|
||||
import { Position } from '@xyflow/react';
|
||||
import { useState } from 'react';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
import { IconTrash } from 'twenty-ui/display';
|
||||
import { FloatingIconButton } from 'twenty-ui/input';
|
||||
import { useConnectionState } from '@/workflow/workflow-diagram/workflow-nodes/hooks/useConnectionState';
|
||||
import { css } from '@emotion/react';
|
||||
import { WorkflowDiagramHandleTarget } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramHandleTarget';
|
||||
import { WorkflowDiagramHandleSource } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramHandleSource';
|
||||
|
||||
const StyledAddStepButtonContainer = styled.div<{
|
||||
shouldDisplay: boolean;
|
||||
@@ -33,7 +35,9 @@ const StyledAddStepButtonContainer = styled.div<{
|
||||
transform: translateX(-50%) translateY(100%);
|
||||
`;
|
||||
|
||||
const StyledNodeContainer = styled(WorkflowNodeContainer)`
|
||||
const StyledNodeContainer = styled(WorkflowNodeContainer)<{
|
||||
isConnectable?: boolean;
|
||||
}>`
|
||||
border-color: ${({ theme }) => theme.border.color.strong};
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
|
||||
@@ -44,6 +48,11 @@ const StyledNodeContainer = styled(WorkflowNodeContainer)`
|
||||
${({ theme }) => theme.background.transparent.lighter} 100%
|
||||
),
|
||||
${({ theme }) => theme.background.secondary};
|
||||
${({ theme, isConnectable }) =>
|
||||
isConnectable &&
|
||||
css`
|
||||
border-color: ${theme.color.blue} !important;
|
||||
`};
|
||||
}
|
||||
|
||||
.selected & {
|
||||
@@ -100,30 +109,21 @@ export const WorkflowDiagramStepNodeEditableContent = ({
|
||||
|
||||
const { isNodeCreationStarted } = useStartNodeCreation();
|
||||
|
||||
const { getNodeHandlesSelectedState, getNodeHandlesHoveredState } =
|
||||
useEdgeState();
|
||||
const { isConnectable, isSourceConnected, isInProgressConnection } =
|
||||
useConnectionState(data.nodeType);
|
||||
|
||||
const handlesSelectedState = getNodeHandlesSelectedState(id);
|
||||
|
||||
const handlesHoveredState = getNodeHandlesHoveredState(id);
|
||||
const { isSourceSelected, isSourceHovered } = useEdgeState();
|
||||
|
||||
return (
|
||||
<>
|
||||
{data.nodeType !== 'trigger' && (
|
||||
<WorkflowDiagramHandleEditable
|
||||
type="target"
|
||||
position={Position.Top}
|
||||
selected={handlesSelectedState.targetHandle}
|
||||
hovered={handlesHoveredState.targetHandle}
|
||||
/>
|
||||
)}
|
||||
|
||||
<StyledNodeContainer
|
||||
data-click-outside-id={WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID}
|
||||
onClick={onClick}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
isConnectable={isConnectable(id)}
|
||||
>
|
||||
<WorkflowDiagramHandleTarget isConnectable={isConnectable(id)} />
|
||||
<WorkflowNodeIconContainer>
|
||||
<WorkflowDiagramStepNodeIcon data={data} />
|
||||
</WorkflowNodeIconContainer>
|
||||
@@ -147,7 +147,7 @@ export const WorkflowDiagramStepNodeEditableContent = ({
|
||||
)}
|
||||
</StyledNodeContainer>
|
||||
|
||||
{!data.hasNextStepIds && (
|
||||
{!data.hasNextStepIds && !isInProgressConnection && (
|
||||
<StyledAddStepButtonContainer
|
||||
shouldDisplay={
|
||||
isHovered ||
|
||||
@@ -161,11 +161,14 @@ export const WorkflowDiagramStepNodeEditableContent = ({
|
||||
</StyledAddStepButtonContainer>
|
||||
)}
|
||||
|
||||
<WorkflowDiagramHandleEditable
|
||||
type="source"
|
||||
position={Position.Bottom}
|
||||
selected={handlesSelectedState.sourceHandle || selected}
|
||||
hovered={handlesHoveredState.sourceHandle}
|
||||
<WorkflowDiagramHandleSource
|
||||
selected={
|
||||
isSourceSelected(id) ||
|
||||
selected ||
|
||||
isSourceConnected(id) ||
|
||||
(isConnectable(id) && isHovered)
|
||||
}
|
||||
hovered={isSourceHovered(id) || isHovered}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
+4
-15
@@ -9,7 +9,6 @@ import { WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID } from '@/workflow/wor
|
||||
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
|
||||
import { type WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
|
||||
import { WorkflowDiagramHandleReadonly } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramHandleReadonly';
|
||||
import { WorkflowDiagramStepNodeIcon } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeIcon';
|
||||
import { WorkflowNodeContainer } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeContainer';
|
||||
import { WorkflowNodeIconContainer } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeIconContainer';
|
||||
@@ -18,11 +17,12 @@ import { WorkflowNodeLabelWithCounterPart } from '@/workflow/workflow-diagram/wo
|
||||
import { WorkflowNodeRightPart } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeRightPart';
|
||||
import { WorkflowNodeTitle } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeTitle';
|
||||
import styled from '@emotion/styled';
|
||||
import { Position } from '@xyflow/react';
|
||||
import { useContext } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { WorkflowDiagramHandleTarget } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramHandleTarget';
|
||||
import { WorkflowDiagramHandleSource } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramHandleSource';
|
||||
|
||||
const StyledNodeContainer = styled(WorkflowNodeContainer)`
|
||||
border-color: ${({ theme }) => theme.border.color.strong};
|
||||
@@ -109,18 +109,11 @@ export const WorkflowDiagramStepNodeReadonly = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
{data.nodeType !== 'trigger' && (
|
||||
<WorkflowDiagramHandleReadonly
|
||||
type="target"
|
||||
position={Position.Top}
|
||||
selected={false}
|
||||
/>
|
||||
)}
|
||||
|
||||
<StyledNodeContainer
|
||||
data-click-outside-id={WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<WorkflowDiagramHandleTarget />
|
||||
<WorkflowNodeIconContainer>
|
||||
<WorkflowDiagramStepNodeIcon data={data} />
|
||||
</WorkflowNodeIconContainer>
|
||||
@@ -134,11 +127,7 @@ export const WorkflowDiagramStepNodeReadonly = ({
|
||||
</WorkflowNodeRightPart>
|
||||
</StyledNodeContainer>
|
||||
|
||||
<WorkflowDiagramHandleReadonly
|
||||
type="source"
|
||||
position={Position.Bottom}
|
||||
selected={selected}
|
||||
/>
|
||||
<WorkflowDiagramHandleSource selected={selected} readOnly />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+1
@@ -13,6 +13,7 @@ const StyledNodeContainer = styled.div`
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: border-color 0.1s;
|
||||
`;
|
||||
|
||||
export { StyledNodeContainer as WorkflowNodeContainer };
|
||||
|
||||
+4
-15
@@ -9,7 +9,6 @@ import { WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID } from '@/workflow/wor
|
||||
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
|
||||
import { type WorkflowRunDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
|
||||
import { WorkflowDiagramHandleReadonly } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramHandleReadonly';
|
||||
import { WorkflowDiagramStepNodeIcon } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeIcon';
|
||||
import { WorkflowNodeContainer } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeContainer';
|
||||
import { WorkflowNodeIconContainer } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeIconContainer';
|
||||
@@ -19,13 +18,14 @@ import { WorkflowNodeRightPart } from '@/workflow/workflow-diagram/workflow-node
|
||||
import { WorkflowNodeTitle } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeTitle';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Position } from '@xyflow/react';
|
||||
import { useContext } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
import { StepStatus } from 'twenty-shared/workflow';
|
||||
import { IconCheck, IconX, useIcons } from 'twenty-ui/display';
|
||||
import { Loader } from 'twenty-ui/feedback';
|
||||
import { WorkflowDiagramHandleTarget } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramHandleTarget';
|
||||
import { WorkflowDiagramHandleSource } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramHandleSource';
|
||||
|
||||
const StyledNodeContainer = styled(WorkflowNodeContainer)`
|
||||
border-color: ${({ theme }) => theme.border.color.strong};
|
||||
@@ -177,19 +177,12 @@ export const WorkflowRunDiagramStepNode = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
{data.nodeType !== 'trigger' && (
|
||||
<WorkflowDiagramHandleReadonly
|
||||
type="target"
|
||||
position={Position.Top}
|
||||
selected={false}
|
||||
/>
|
||||
)}
|
||||
|
||||
<StyledNodeContainer
|
||||
data-click-outside-id={WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID}
|
||||
data-status={data.runStatus}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<WorkflowDiagramHandleTarget />
|
||||
<WorkflowNodeIconContainer>
|
||||
<WorkflowDiagramStepNodeIcon data={data} />
|
||||
</WorkflowNodeIconContainer>
|
||||
@@ -230,11 +223,7 @@ export const WorkflowRunDiagramStepNode = ({
|
||||
</WorkflowNodeRightPart>
|
||||
</StyledNodeContainer>
|
||||
|
||||
<WorkflowDiagramHandleReadonly
|
||||
type="source"
|
||||
position={Position.Bottom}
|
||||
selected={selected}
|
||||
/>
|
||||
<WorkflowDiagramHandleSource selected={selected} readOnly />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { useConnection, useEdges } from '@xyflow/react';
|
||||
|
||||
export const useConnectionState = (nodeType: 'action' | 'trigger') => {
|
||||
const connection = useConnection();
|
||||
|
||||
const edges = useEdges();
|
||||
|
||||
const isInProgressConnection = connection.inProgress;
|
||||
|
||||
const isConnectable = (nodeId: string) => {
|
||||
if (nodeType === 'trigger') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isInProgressConnection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (connection.fromNode.id === nodeId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !edges.some(
|
||||
(edge) =>
|
||||
edge.target === nodeId &&
|
||||
connection.fromNode?.id &&
|
||||
edge.source === connection.fromNode.id,
|
||||
);
|
||||
};
|
||||
|
||||
const isSourceConnected = (nodeId: string) => {
|
||||
return connection.inProgress && connection.fromNode.id === nodeId;
|
||||
};
|
||||
|
||||
return { isConnectable, isSourceConnected, isInProgressConnection };
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useGetUpdatableWorkflowVersionOrThrow } from '@/workflow/hooks/useGetUpdatableWorkflowVersionOrThrow';
|
||||
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagramEdge';
|
||||
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdge';
|
||||
import { useCreateWorkflowVersionEdge } from '@/workflow/workflow-steps/hooks/useCreateWorkflowVersionEdge';
|
||||
import { useState } from 'react';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useGetUpdatableWorkflowVersionOrThrow } from '@/workflow/hooks/useGetUpdatableWorkflowVersionOrThrow';
|
||||
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagramEdge';
|
||||
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdge';
|
||||
import { useDeleteWorkflowVersionEdge } from '@/workflow/workflow-steps/hooks/useDeleteWorkflowVersionEdge';
|
||||
import { useState } from 'react';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user