Skip to content

Commit 82d60c7

Browse files
corentin-hoareauCorentinHenryHengZJ
authored
Feature: extends ReactFlow controls with snapping functionality (#4482)
* Feature: extends ReactFlow controls with snapping functionality * Adds snapping on other flows * lint fix, add dark mode, fix marketplace canvas --------- Co-authored-by: Corentin <corentin.hoareau@sogeti.com> Co-authored-by: Henry <hzj94@hotmail.com>
1 parent 4326cbe commit 82d60c7

File tree

6 files changed

+158
-11
lines changed

6 files changed

+158
-11
lines changed

packages/ui/src/views/agentflowsv2/Canvas.jsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import useApi from '@/hooks/useApi'
4242
import useConfirm from '@/hooks/useConfirm'
4343

4444
// icons
45-
import { IconX, IconRefreshAlert } from '@tabler/icons-react'
45+
import { IconX, IconRefreshAlert, IconMagnetFilled, IconMagnetOff } from '@tabler/icons-react'
4646

4747
// utils
4848
import {
@@ -100,6 +100,7 @@ const AgentflowCanvas = () => {
100100
const [isSyncNodesButtonEnabled, setIsSyncNodesButtonEnabled] = useState(false)
101101
const [editNodeDialogOpen, setEditNodeDialogOpen] = useState(false)
102102
const [editNodeDialogProps, setEditNodeDialogProps] = useState({})
103+
const [isSnappingEnabled, setIsSnappingEnabled] = useState(false)
103104

104105
const reactFlowWrapper = useRef(null)
105106

@@ -718,17 +719,30 @@ const AgentflowCanvas = () => {
718719
fitView
719720
deleteKeyCode={canvas.canvasDialogShow ? null : ['Delete']}
720721
minZoom={0.5}
722+
snapGrid={[25, 25]}
723+
snapToGrid={isSnappingEnabled}
721724
connectionLineComponent={ConnectionLine}
722725
>
723726
<Controls
727+
className={customization.isDarkMode ? 'dark-mode-controls' : ''}
724728
style={{
725729
display: 'flex',
726730
flexDirection: 'row',
727731
left: '50%',
728-
transform: 'translate(-50%, -50%)',
729-
backgroundColor: customization.isDarkMode ? theme.palette.background.default : '#fff'
732+
transform: 'translate(-50%, -50%)'
730733
}}
731-
/>
734+
>
735+
<button
736+
className='react-flow__controls-button react-flow__controls-interactive'
737+
onClick={() => {
738+
setIsSnappingEnabled(!isSnappingEnabled)
739+
}}
740+
title='toggle snapping'
741+
aria-label='toggle snapping'
742+
>
743+
{isSnappingEnabled ? <IconMagnetFilled /> : <IconMagnetOff />}
744+
</button>
745+
</Controls>
732746
<MiniMap
733747
nodeStrokeWidth={3}
734748
nodeColor={customization.isDarkMode ? '#2d2d2d' : '#e2e2e2'}

packages/ui/src/views/agentflowsv2/MarketplaceCanvas.jsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'reactflow/dist/style.css'
44
import '@/views/canvas/index.css'
55

66
import { useLocation, useNavigate } from 'react-router-dom'
7+
import { useSelector } from 'react-redux'
78

89
// material-ui
910
import { Toolbar, Box, AppBar } from '@mui/material'
@@ -18,6 +19,9 @@ import StickyNote from './StickyNote'
1819
import EditNodeDialog from '@/views/agentflowsv2/EditNodeDialog'
1920
import { flowContext } from '@/store/context/ReactFlowContext'
2021

22+
// icons
23+
import { IconMagnetFilled, IconMagnetOff } from '@tabler/icons-react'
24+
2125
const nodeTypes = { agentFlow: AgentFlowNode, stickyNote: StickyNote, iteration: IterationNode }
2226
const edgeTypes = { agentFlow: AgentFlowEdge }
2327

@@ -26,6 +30,7 @@ const edgeTypes = { agentFlow: AgentFlowEdge }
2630
const MarketplaceCanvasV2 = () => {
2731
const theme = useTheme()
2832
const navigate = useNavigate()
33+
const customization = useSelector((state) => state.customization)
2934

3035
const { state } = useLocation()
3136
const { flowData, name } = state
@@ -36,6 +41,7 @@ const MarketplaceCanvasV2 = () => {
3641
const [edges, setEdges, onEdgesChange] = useEdgesState()
3742
const [editNodeDialogOpen, setEditNodeDialogOpen] = useState(false)
3843
const [editNodeDialogProps, setEditNodeDialogProps] = useState({})
44+
const [isSnappingEnabled, setIsSnappingEnabled] = useState(false)
3945

4046
const reactFlowWrapper = useRef(null)
4147
const { setReactFlowInstance } = useContext(flowContext)
@@ -108,15 +114,29 @@ const MarketplaceCanvasV2 = () => {
108114
edgeTypes={edgeTypes}
109115
fitView
110116
minZoom={0.1}
117+
snapGrid={[25, 25]}
118+
snapToGrid={isSnappingEnabled}
111119
>
112120
<Controls
121+
className={customization.isDarkMode ? 'dark-mode-controls' : ''}
113122
style={{
114123
display: 'flex',
115124
flexDirection: 'row',
116125
left: '50%',
117126
transform: 'translate(-50%, -50%)'
118127
}}
119-
/>
128+
>
129+
<button
130+
className='react-flow__controls-button react-flow__controls-interactive'
131+
onClick={() => {
132+
setIsSnappingEnabled(!isSnappingEnabled)
133+
}}
134+
title='toggle snapping'
135+
aria-label='toggle snapping'
136+
>
137+
{isSnappingEnabled ? <IconMagnetFilled /> : <IconMagnetOff />}
138+
</button>
139+
</Controls>
120140
<Background color='#aaa' gap={16} />
121141
<EditNodeDialog
122142
show={editNodeDialogOpen}

packages/ui/src/views/agentflowsv2/index.css

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,42 @@
5454
stroke-width: 3 !important;
5555
opacity: 1;
5656
}
57+
58+
/* Dark mode controls styling */
59+
.dark-mode-controls {
60+
--xy-controls-button-background-color-default: #2d2d2d;
61+
--xy-controls-button-background-color-hover-default: #404040;
62+
--xy-controls-button-border-color-default: #525252;
63+
--xy-controls-box-shadow-default: 0 0 2px 1px rgba(255, 255, 255, 0.1);
64+
}
65+
66+
.dark-mode-controls .react-flow__controls-button {
67+
background-color: #2d2d2d;
68+
border-color: #525252;
69+
color: #ffffff;
70+
border: 1px solid #525252;
71+
}
72+
73+
.dark-mode-controls .react-flow__controls-button:hover {
74+
background-color: #404040;
75+
}
76+
77+
.dark-mode-controls .react-flow__controls-button.react-flow__controls-interactive {
78+
background-color: #2d2d2d;
79+
border-color: #525252;
80+
color: #ffffff;
81+
}
82+
83+
.dark-mode-controls .react-flow__controls-button.react-flow__controls-interactive:hover {
84+
background-color: #404040;
85+
}
86+
87+
.dark-mode-controls .react-flow__controls-button svg {
88+
color: #ffffff;
89+
fill: #ffffff;
90+
}
91+
92+
.dark-mode-controls .react-flow__controls-button:hover svg {
93+
color: #ffffff;
94+
fill: #ffffff;
95+
}

packages/ui/src/views/canvas/index.css

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,42 @@
4747
cursor: crosshair;
4848
background: #5dba62 !important;
4949
}
50+
51+
/* Dark mode controls styling */
52+
.dark-mode-controls {
53+
--xy-controls-button-background-color-default: #2d2d2d;
54+
--xy-controls-button-background-color-hover-default: #404040;
55+
--xy-controls-button-border-color-default: #525252;
56+
--xy-controls-box-shadow-default: 0 0 2px 1px rgba(255, 255, 255, 0.1);
57+
}
58+
59+
.dark-mode-controls .react-flow__controls-button {
60+
background-color: #2d2d2d;
61+
border-color: #525252;
62+
color: #ffffff;
63+
border: 1px solid #525252;
64+
}
65+
66+
.dark-mode-controls .react-flow__controls-button:hover {
67+
background-color: #404040;
68+
}
69+
70+
.dark-mode-controls .react-flow__controls-button.react-flow__controls-interactive {
71+
background-color: #2d2d2d;
72+
border-color: #525252;
73+
color: #ffffff;
74+
}
75+
76+
.dark-mode-controls .react-flow__controls-button.react-flow__controls-interactive:hover {
77+
background-color: #404040;
78+
}
79+
80+
.dark-mode-controls .react-flow__controls-button svg {
81+
color: #ffffff;
82+
fill: #ffffff;
83+
}
84+
85+
.dark-mode-controls .react-flow__controls-button:hover svg {
86+
color: #ffffff;
87+
fill: #ffffff;
88+
}

packages/ui/src/views/canvas/index.jsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import useConfirm from '@/hooks/useConfirm'
3838
import { useAuth } from '@/hooks/useAuth'
3939

4040
// icons
41-
import { IconX, IconRefreshAlert } from '@tabler/icons-react'
41+
import { IconX, IconRefreshAlert, IconMagnetFilled, IconMagnetOff } from '@tabler/icons-react'
4242

4343
// utils
4444
import {
@@ -77,6 +77,7 @@ const Canvas = () => {
7777
const { confirm } = useConfirm()
7878

7979
const dispatch = useDispatch()
80+
const customization = useSelector((state) => state.customization)
8081
const canvas = useSelector((state) => state.canvas)
8182
const [canvasDataStore, setCanvasDataStore] = useState(canvas)
8283
const [chatflow, setChatflow] = useState(null)
@@ -96,6 +97,7 @@ const Canvas = () => {
9697
const [selectedNode, setSelectedNode] = useState(null)
9798
const [isUpsertButtonEnabled, setIsUpsertButtonEnabled] = useState(false)
9899
const [isSyncNodesButtonEnabled, setIsSyncNodesButtonEnabled] = useState(false)
100+
const [isSnappingEnabled, setIsSnappingEnabled] = useState(false)
99101

100102
const reactFlowWrapper = useRef(null)
101103

@@ -596,16 +598,30 @@ const Canvas = () => {
596598
fitView
597599
deleteKeyCode={canvas.canvasDialogShow ? null : ['Delete']}
598600
minZoom={0.1}
601+
snapGrid={[25, 25]}
602+
snapToGrid={isSnappingEnabled}
599603
className='chatflow-canvas'
600604
>
601605
<Controls
606+
className={customization.isDarkMode ? 'dark-mode-controls' : ''}
602607
style={{
603608
display: 'flex',
604609
flexDirection: 'row',
605610
left: '50%',
606611
transform: 'translate(-50%, -50%)'
607612
}}
608-
/>
613+
>
614+
<button
615+
className='react-flow__controls-button react-flow__controls-interactive'
616+
onClick={() => {
617+
setIsSnappingEnabled(!isSnappingEnabled)
618+
}}
619+
title='toggle snapping'
620+
aria-label='toggle snapping'
621+
>
622+
{isSnappingEnabled ? <IconMagnetFilled /> : <IconMagnetOff />}
623+
</button>
624+
</Controls>
609625
<Background color='#aaa' gap={16} />
610626
<AddNodes isAgentCanvas={isAgentCanvas} nodesData={getNodesApi.data} node={selectedNode} />
611627
{isSyncNodesButtonEnabled && (

packages/ui/src/views/marketplaces/MarketplaceCanvas.jsx

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { useEffect, useRef } from 'react'
1+
import { useEffect, useRef, useState } from 'react'
22
import ReactFlow, { Controls, Background, useNodesState, useEdgesState } from 'reactflow'
33
import 'reactflow/dist/style.css'
44
import '@/views/canvas/index.css'
55

66
import { useLocation, useNavigate } from 'react-router-dom'
7+
import { useSelector } from 'react-redux'
78

89
// material-ui
910
import { Toolbar, Box, AppBar } from '@mui/material'
@@ -14,6 +15,9 @@ import MarketplaceCanvasNode from './MarketplaceCanvasNode'
1415
import MarketplaceCanvasHeader from './MarketplaceCanvasHeader'
1516
import StickyNote from '../canvas/StickyNote'
1617

18+
// icons
19+
import { IconMagnetFilled, IconMagnetOff } from '@tabler/icons-react'
20+
1721
const nodeTypes = { customNode: MarketplaceCanvasNode, stickyNote: StickyNote }
1822
const edgeTypes = { buttonedge: '' }
1923

@@ -22,15 +26,16 @@ const edgeTypes = { buttonedge: '' }
2226
const MarketplaceCanvas = () => {
2327
const theme = useTheme()
2428
const navigate = useNavigate()
29+
const customization = useSelector((state) => state.customization)
2530

2631
const { state } = useLocation()
27-
const flowData = state?.flowData || '{}'
28-
const name = state?.name || 'Untitled'
32+
const { flowData, name } = state
2933

3034
// ==============================|| ReactFlow ||============================== //
3135

3236
const [nodes, setNodes, onNodesChange] = useNodesState()
3337
const [edges, setEdges, onEdgesChange] = useEdgesState()
38+
const [isSnappingEnabled, setIsSnappingEnabled] = useState(false)
3439

3540
const reactFlowWrapper = useRef(null)
3641

@@ -87,15 +92,29 @@ const MarketplaceCanvas = () => {
8792
edgeTypes={edgeTypes}
8893
fitView
8994
minZoom={0.1}
95+
snapGrid={[25, 25]}
96+
snapToGrid={isSnappingEnabled}
9097
>
9198
<Controls
99+
className={customization.isDarkMode ? 'dark-mode-controls' : ''}
92100
style={{
93101
display: 'flex',
94102
flexDirection: 'row',
95103
left: '50%',
96104
transform: 'translate(-50%, -50%)'
97105
}}
98-
/>
106+
>
107+
<button
108+
className='react-flow__controls-button react-flow__controls-interactive'
109+
onClick={() => {
110+
setIsSnappingEnabled(!isSnappingEnabled)
111+
}}
112+
title='toggle snapping'
113+
aria-label='toggle snapping'
114+
>
115+
{isSnappingEnabled ? <IconMagnetFilled /> : <IconMagnetOff />}
116+
</button>
117+
</Controls>
99118
<Background color='#aaa' gap={16} />
100119
</ReactFlow>
101120
</div>

0 commit comments

Comments
 (0)