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>
This commit is contained in:
parent
4326cbe6b5
commit
82d60c7d15
|
|
@ -42,7 +42,7 @@ import useApi from '@/hooks/useApi'
|
||||||
import useConfirm from '@/hooks/useConfirm'
|
import useConfirm from '@/hooks/useConfirm'
|
||||||
|
|
||||||
// icons
|
// icons
|
||||||
import { IconX, IconRefreshAlert } from '@tabler/icons-react'
|
import { IconX, IconRefreshAlert, IconMagnetFilled, IconMagnetOff } from '@tabler/icons-react'
|
||||||
|
|
||||||
// utils
|
// utils
|
||||||
import {
|
import {
|
||||||
|
|
@ -100,6 +100,7 @@ const AgentflowCanvas = () => {
|
||||||
const [isSyncNodesButtonEnabled, setIsSyncNodesButtonEnabled] = useState(false)
|
const [isSyncNodesButtonEnabled, setIsSyncNodesButtonEnabled] = useState(false)
|
||||||
const [editNodeDialogOpen, setEditNodeDialogOpen] = useState(false)
|
const [editNodeDialogOpen, setEditNodeDialogOpen] = useState(false)
|
||||||
const [editNodeDialogProps, setEditNodeDialogProps] = useState({})
|
const [editNodeDialogProps, setEditNodeDialogProps] = useState({})
|
||||||
|
const [isSnappingEnabled, setIsSnappingEnabled] = useState(false)
|
||||||
|
|
||||||
const reactFlowWrapper = useRef(null)
|
const reactFlowWrapper = useRef(null)
|
||||||
|
|
||||||
|
|
@ -718,17 +719,30 @@ const AgentflowCanvas = () => {
|
||||||
fitView
|
fitView
|
||||||
deleteKeyCode={canvas.canvasDialogShow ? null : ['Delete']}
|
deleteKeyCode={canvas.canvasDialogShow ? null : ['Delete']}
|
||||||
minZoom={0.5}
|
minZoom={0.5}
|
||||||
|
snapGrid={[25, 25]}
|
||||||
|
snapToGrid={isSnappingEnabled}
|
||||||
connectionLineComponent={ConnectionLine}
|
connectionLineComponent={ConnectionLine}
|
||||||
>
|
>
|
||||||
<Controls
|
<Controls
|
||||||
|
className={customization.isDarkMode ? 'dark-mode-controls' : ''}
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: 'translate(-50%, -50%)'
|
||||||
backgroundColor: customization.isDarkMode ? theme.palette.background.default : '#fff'
|
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
<button
|
||||||
|
className='react-flow__controls-button react-flow__controls-interactive'
|
||||||
|
onClick={() => {
|
||||||
|
setIsSnappingEnabled(!isSnappingEnabled)
|
||||||
|
}}
|
||||||
|
title='toggle snapping'
|
||||||
|
aria-label='toggle snapping'
|
||||||
|
>
|
||||||
|
{isSnappingEnabled ? <IconMagnetFilled /> : <IconMagnetOff />}
|
||||||
|
</button>
|
||||||
|
</Controls>
|
||||||
<MiniMap
|
<MiniMap
|
||||||
nodeStrokeWidth={3}
|
nodeStrokeWidth={3}
|
||||||
nodeColor={customization.isDarkMode ? '#2d2d2d' : '#e2e2e2'}
|
nodeColor={customization.isDarkMode ? '#2d2d2d' : '#e2e2e2'}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import 'reactflow/dist/style.css'
|
||||||
import '@/views/canvas/index.css'
|
import '@/views/canvas/index.css'
|
||||||
|
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import { Toolbar, Box, AppBar } from '@mui/material'
|
import { Toolbar, Box, AppBar } from '@mui/material'
|
||||||
|
|
@ -18,6 +19,9 @@ import StickyNote from './StickyNote'
|
||||||
import EditNodeDialog from '@/views/agentflowsv2/EditNodeDialog'
|
import EditNodeDialog from '@/views/agentflowsv2/EditNodeDialog'
|
||||||
import { flowContext } from '@/store/context/ReactFlowContext'
|
import { flowContext } from '@/store/context/ReactFlowContext'
|
||||||
|
|
||||||
|
// icons
|
||||||
|
import { IconMagnetFilled, IconMagnetOff } from '@tabler/icons-react'
|
||||||
|
|
||||||
const nodeTypes = { agentFlow: AgentFlowNode, stickyNote: StickyNote, iteration: IterationNode }
|
const nodeTypes = { agentFlow: AgentFlowNode, stickyNote: StickyNote, iteration: IterationNode }
|
||||||
const edgeTypes = { agentFlow: AgentFlowEdge }
|
const edgeTypes = { agentFlow: AgentFlowEdge }
|
||||||
|
|
||||||
|
|
@ -26,6 +30,7 @@ const edgeTypes = { agentFlow: AgentFlowEdge }
|
||||||
const MarketplaceCanvasV2 = () => {
|
const MarketplaceCanvasV2 = () => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const customization = useSelector((state) => state.customization)
|
||||||
|
|
||||||
const { state } = useLocation()
|
const { state } = useLocation()
|
||||||
const { flowData, name } = state
|
const { flowData, name } = state
|
||||||
|
|
@ -36,6 +41,7 @@ const MarketplaceCanvasV2 = () => {
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState()
|
const [edges, setEdges, onEdgesChange] = useEdgesState()
|
||||||
const [editNodeDialogOpen, setEditNodeDialogOpen] = useState(false)
|
const [editNodeDialogOpen, setEditNodeDialogOpen] = useState(false)
|
||||||
const [editNodeDialogProps, setEditNodeDialogProps] = useState({})
|
const [editNodeDialogProps, setEditNodeDialogProps] = useState({})
|
||||||
|
const [isSnappingEnabled, setIsSnappingEnabled] = useState(false)
|
||||||
|
|
||||||
const reactFlowWrapper = useRef(null)
|
const reactFlowWrapper = useRef(null)
|
||||||
const { setReactFlowInstance } = useContext(flowContext)
|
const { setReactFlowInstance } = useContext(flowContext)
|
||||||
|
|
@ -108,15 +114,29 @@ const MarketplaceCanvasV2 = () => {
|
||||||
edgeTypes={edgeTypes}
|
edgeTypes={edgeTypes}
|
||||||
fitView
|
fitView
|
||||||
minZoom={0.1}
|
minZoom={0.1}
|
||||||
|
snapGrid={[25, 25]}
|
||||||
|
snapToGrid={isSnappingEnabled}
|
||||||
>
|
>
|
||||||
<Controls
|
<Controls
|
||||||
|
className={customization.isDarkMode ? 'dark-mode-controls' : ''}
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translate(-50%, -50%)'
|
transform: 'translate(-50%, -50%)'
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
<button
|
||||||
|
className='react-flow__controls-button react-flow__controls-interactive'
|
||||||
|
onClick={() => {
|
||||||
|
setIsSnappingEnabled(!isSnappingEnabled)
|
||||||
|
}}
|
||||||
|
title='toggle snapping'
|
||||||
|
aria-label='toggle snapping'
|
||||||
|
>
|
||||||
|
{isSnappingEnabled ? <IconMagnetFilled /> : <IconMagnetOff />}
|
||||||
|
</button>
|
||||||
|
</Controls>
|
||||||
<Background color='#aaa' gap={16} />
|
<Background color='#aaa' gap={16} />
|
||||||
<EditNodeDialog
|
<EditNodeDialog
|
||||||
show={editNodeDialogOpen}
|
show={editNodeDialogOpen}
|
||||||
|
|
|
||||||
|
|
@ -54,3 +54,42 @@
|
||||||
stroke-width: 3 !important;
|
stroke-width: 3 !important;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dark mode controls styling */
|
||||||
|
.dark-mode-controls {
|
||||||
|
--xy-controls-button-background-color-default: #2d2d2d;
|
||||||
|
--xy-controls-button-background-color-hover-default: #404040;
|
||||||
|
--xy-controls-button-border-color-default: #525252;
|
||||||
|
--xy-controls-box-shadow-default: 0 0 2px 1px rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode-controls .react-flow__controls-button {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border-color: #525252;
|
||||||
|
color: #ffffff;
|
||||||
|
border: 1px solid #525252;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode-controls .react-flow__controls-button:hover {
|
||||||
|
background-color: #404040;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode-controls .react-flow__controls-button.react-flow__controls-interactive {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border-color: #525252;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode-controls .react-flow__controls-button.react-flow__controls-interactive:hover {
|
||||||
|
background-color: #404040;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode-controls .react-flow__controls-button svg {
|
||||||
|
color: #ffffff;
|
||||||
|
fill: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode-controls .react-flow__controls-button:hover svg {
|
||||||
|
color: #ffffff;
|
||||||
|
fill: #ffffff;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,3 +47,42 @@
|
||||||
cursor: crosshair;
|
cursor: crosshair;
|
||||||
background: #5dba62 !important;
|
background: #5dba62 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dark mode controls styling */
|
||||||
|
.dark-mode-controls {
|
||||||
|
--xy-controls-button-background-color-default: #2d2d2d;
|
||||||
|
--xy-controls-button-background-color-hover-default: #404040;
|
||||||
|
--xy-controls-button-border-color-default: #525252;
|
||||||
|
--xy-controls-box-shadow-default: 0 0 2px 1px rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode-controls .react-flow__controls-button {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border-color: #525252;
|
||||||
|
color: #ffffff;
|
||||||
|
border: 1px solid #525252;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode-controls .react-flow__controls-button:hover {
|
||||||
|
background-color: #404040;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode-controls .react-flow__controls-button.react-flow__controls-interactive {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border-color: #525252;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode-controls .react-flow__controls-button.react-flow__controls-interactive:hover {
|
||||||
|
background-color: #404040;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode-controls .react-flow__controls-button svg {
|
||||||
|
color: #ffffff;
|
||||||
|
fill: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode-controls .react-flow__controls-button:hover svg {
|
||||||
|
color: #ffffff;
|
||||||
|
fill: #ffffff;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ import useConfirm from '@/hooks/useConfirm'
|
||||||
import { useAuth } from '@/hooks/useAuth'
|
import { useAuth } from '@/hooks/useAuth'
|
||||||
|
|
||||||
// icons
|
// icons
|
||||||
import { IconX, IconRefreshAlert } from '@tabler/icons-react'
|
import { IconX, IconRefreshAlert, IconMagnetFilled, IconMagnetOff } from '@tabler/icons-react'
|
||||||
|
|
||||||
// utils
|
// utils
|
||||||
import {
|
import {
|
||||||
|
|
@ -77,6 +77,7 @@ const Canvas = () => {
|
||||||
const { confirm } = useConfirm()
|
const { confirm } = useConfirm()
|
||||||
|
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
const customization = useSelector((state) => state.customization)
|
||||||
const canvas = useSelector((state) => state.canvas)
|
const canvas = useSelector((state) => state.canvas)
|
||||||
const [canvasDataStore, setCanvasDataStore] = useState(canvas)
|
const [canvasDataStore, setCanvasDataStore] = useState(canvas)
|
||||||
const [chatflow, setChatflow] = useState(null)
|
const [chatflow, setChatflow] = useState(null)
|
||||||
|
|
@ -96,6 +97,7 @@ const Canvas = () => {
|
||||||
const [selectedNode, setSelectedNode] = useState(null)
|
const [selectedNode, setSelectedNode] = useState(null)
|
||||||
const [isUpsertButtonEnabled, setIsUpsertButtonEnabled] = useState(false)
|
const [isUpsertButtonEnabled, setIsUpsertButtonEnabled] = useState(false)
|
||||||
const [isSyncNodesButtonEnabled, setIsSyncNodesButtonEnabled] = useState(false)
|
const [isSyncNodesButtonEnabled, setIsSyncNodesButtonEnabled] = useState(false)
|
||||||
|
const [isSnappingEnabled, setIsSnappingEnabled] = useState(false)
|
||||||
|
|
||||||
const reactFlowWrapper = useRef(null)
|
const reactFlowWrapper = useRef(null)
|
||||||
|
|
||||||
|
|
@ -596,16 +598,30 @@ const Canvas = () => {
|
||||||
fitView
|
fitView
|
||||||
deleteKeyCode={canvas.canvasDialogShow ? null : ['Delete']}
|
deleteKeyCode={canvas.canvasDialogShow ? null : ['Delete']}
|
||||||
minZoom={0.1}
|
minZoom={0.1}
|
||||||
|
snapGrid={[25, 25]}
|
||||||
|
snapToGrid={isSnappingEnabled}
|
||||||
className='chatflow-canvas'
|
className='chatflow-canvas'
|
||||||
>
|
>
|
||||||
<Controls
|
<Controls
|
||||||
|
className={customization.isDarkMode ? 'dark-mode-controls' : ''}
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translate(-50%, -50%)'
|
transform: 'translate(-50%, -50%)'
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
<button
|
||||||
|
className='react-flow__controls-button react-flow__controls-interactive'
|
||||||
|
onClick={() => {
|
||||||
|
setIsSnappingEnabled(!isSnappingEnabled)
|
||||||
|
}}
|
||||||
|
title='toggle snapping'
|
||||||
|
aria-label='toggle snapping'
|
||||||
|
>
|
||||||
|
{isSnappingEnabled ? <IconMagnetFilled /> : <IconMagnetOff />}
|
||||||
|
</button>
|
||||||
|
</Controls>
|
||||||
<Background color='#aaa' gap={16} />
|
<Background color='#aaa' gap={16} />
|
||||||
<AddNodes isAgentCanvas={isAgentCanvas} nodesData={getNodesApi.data} node={selectedNode} />
|
<AddNodes isAgentCanvas={isAgentCanvas} nodesData={getNodesApi.data} node={selectedNode} />
|
||||||
{isSyncNodesButtonEnabled && (
|
{isSyncNodesButtonEnabled && (
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import ReactFlow, { Controls, Background, useNodesState, useEdgesState } from 'reactflow'
|
import ReactFlow, { Controls, Background, useNodesState, useEdgesState } from 'reactflow'
|
||||||
import 'reactflow/dist/style.css'
|
import 'reactflow/dist/style.css'
|
||||||
import '@/views/canvas/index.css'
|
import '@/views/canvas/index.css'
|
||||||
|
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import { Toolbar, Box, AppBar } from '@mui/material'
|
import { Toolbar, Box, AppBar } from '@mui/material'
|
||||||
|
|
@ -14,6 +15,9 @@ import MarketplaceCanvasNode from './MarketplaceCanvasNode'
|
||||||
import MarketplaceCanvasHeader from './MarketplaceCanvasHeader'
|
import MarketplaceCanvasHeader from './MarketplaceCanvasHeader'
|
||||||
import StickyNote from '../canvas/StickyNote'
|
import StickyNote from '../canvas/StickyNote'
|
||||||
|
|
||||||
|
// icons
|
||||||
|
import { IconMagnetFilled, IconMagnetOff } from '@tabler/icons-react'
|
||||||
|
|
||||||
const nodeTypes = { customNode: MarketplaceCanvasNode, stickyNote: StickyNote }
|
const nodeTypes = { customNode: MarketplaceCanvasNode, stickyNote: StickyNote }
|
||||||
const edgeTypes = { buttonedge: '' }
|
const edgeTypes = { buttonedge: '' }
|
||||||
|
|
||||||
|
|
@ -22,15 +26,16 @@ const edgeTypes = { buttonedge: '' }
|
||||||
const MarketplaceCanvas = () => {
|
const MarketplaceCanvas = () => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const customization = useSelector((state) => state.customization)
|
||||||
|
|
||||||
const { state } = useLocation()
|
const { state } = useLocation()
|
||||||
const flowData = state?.flowData || '{}'
|
const { flowData, name } = state
|
||||||
const name = state?.name || 'Untitled'
|
|
||||||
|
|
||||||
// ==============================|| ReactFlow ||============================== //
|
// ==============================|| ReactFlow ||============================== //
|
||||||
|
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState()
|
const [nodes, setNodes, onNodesChange] = useNodesState()
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState()
|
const [edges, setEdges, onEdgesChange] = useEdgesState()
|
||||||
|
const [isSnappingEnabled, setIsSnappingEnabled] = useState(false)
|
||||||
|
|
||||||
const reactFlowWrapper = useRef(null)
|
const reactFlowWrapper = useRef(null)
|
||||||
|
|
||||||
|
|
@ -87,15 +92,29 @@ const MarketplaceCanvas = () => {
|
||||||
edgeTypes={edgeTypes}
|
edgeTypes={edgeTypes}
|
||||||
fitView
|
fitView
|
||||||
minZoom={0.1}
|
minZoom={0.1}
|
||||||
|
snapGrid={[25, 25]}
|
||||||
|
snapToGrid={isSnappingEnabled}
|
||||||
>
|
>
|
||||||
<Controls
|
<Controls
|
||||||
|
className={customization.isDarkMode ? 'dark-mode-controls' : ''}
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translate(-50%, -50%)'
|
transform: 'translate(-50%, -50%)'
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
<button
|
||||||
|
className='react-flow__controls-button react-flow__controls-interactive'
|
||||||
|
onClick={() => {
|
||||||
|
setIsSnappingEnabled(!isSnappingEnabled)
|
||||||
|
}}
|
||||||
|
title='toggle snapping'
|
||||||
|
aria-label='toggle snapping'
|
||||||
|
>
|
||||||
|
{isSnappingEnabled ? <IconMagnetFilled /> : <IconMagnetOff />}
|
||||||
|
</button>
|
||||||
|
</Controls>
|
||||||
<Background color='#aaa' gap={16} />
|
<Background color='#aaa' gap={16} />
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue