mirror of
https://github.com/langchain-ai/langgraph-builder.git
synced 2026-07-01 19:55:58 -04:00
feat: ✨ edit edge label without need for modal, generate code modal responsive
This commit is contained in:
+132
-142
@@ -1,4 +1,4 @@
|
||||
"use client"
|
||||
'use client'
|
||||
import { useCallback, useState, useEffect, useRef } from 'react'
|
||||
import {
|
||||
Background,
|
||||
@@ -26,7 +26,8 @@ import { useButtonText } from '@/contexts/ButtonTextContext'
|
||||
import Modal from './Modal'
|
||||
import { useEdgeLabel } from '@/contexts/EdgeLabelContext'
|
||||
import EdgeLabelModal from './EdgeLabelModal'
|
||||
import { Button, Modal as MuiModal, ModalDialog } from "@mui/joy"
|
||||
import { Button, Modal as MuiModal, ModalDialog } from '@mui/joy'
|
||||
|
||||
import GenericModal from './GenericModal'
|
||||
|
||||
export default function App() {
|
||||
@@ -57,132 +58,145 @@ export default function App() {
|
||||
showConditionalEdgeModal: false,
|
||||
showRenameModal: false,
|
||||
showGenerateCodeModal: false,
|
||||
});
|
||||
})
|
||||
|
||||
const isNodeOneCreated = nodes.length > 2;
|
||||
const isEdgeOneCreated = edges.length > 0;
|
||||
const isConditionalEdgeCreated = edges.filter((edge) => edge.animated).length > 0;
|
||||
const isNodeOneCreated = nodes.length > 2
|
||||
const isEdgeOneCreated = edges.length > 0
|
||||
const isConditionalEdgeCreated = edges.filter((edge) => edge.animated).length > 0
|
||||
|
||||
useEffect(() => {
|
||||
const isWelcomeModalDismissed = localStorage.getItem('welcomeModalDismissed');
|
||||
const isWelcomeModalDismissed = localStorage.getItem('welcomeModalDismissed')
|
||||
if (isWelcomeModalDismissed !== 'true') {
|
||||
setModals({ ...modals, showWelcomeModal: true });
|
||||
setModals({ ...modals, showWelcomeModal: true })
|
||||
} else {
|
||||
const isNodeModalDismissed = localStorage.getItem('createNodeModalDismissed');
|
||||
const isNodeModalDismissed = localStorage.getItem('createNodeModalDismissed')
|
||||
if (isNodeModalDismissed !== 'true') {
|
||||
setModals({ ...modals, showCreateNodeModal: true });
|
||||
setModals({ ...modals, showCreateNodeModal: true })
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
const handleWelcomeModalClose = () => {
|
||||
setModals((prevModals) => ({ ...prevModals, showWelcomeModal: false }));
|
||||
localStorage.setItem('welcomeModalDismissed', 'true');
|
||||
setModals((prevModals) => ({ ...prevModals, showWelcomeModal: false }))
|
||||
localStorage.setItem('welcomeModalDismissed', 'true')
|
||||
|
||||
const isNodeModalDismissed = localStorage.getItem('createNodeModalDismissed');
|
||||
const isNodeModalDismissed = localStorage.getItem('createNodeModalDismissed')
|
||||
if (isNodeModalDismissed !== 'true') {
|
||||
setModals((prevModals) => ({ ...prevModals, showCreateNodeModal: true }));
|
||||
setModals((prevModals) => ({ ...prevModals, showCreateNodeModal: true }))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleCreateNodeModalClose = () => {
|
||||
if (isNodeOneCreated) {
|
||||
setModals((prevModals) => ({ ...prevModals, showCreateNodeModal: false }));
|
||||
localStorage.setItem("createNodeModalDismissed", "true");
|
||||
setModals((prevModals) => ({ ...prevModals, showCreateEdgeModal: true }));
|
||||
setModals((prevModals) => ({ ...prevModals, showCreateNodeModal: false }))
|
||||
localStorage.setItem('createNodeModalDismissed', 'true')
|
||||
setModals((prevModals) => ({ ...prevModals, showCreateEdgeModal: true }))
|
||||
} else {
|
||||
alert("Please create a node before continuing!");
|
||||
alert('Please create a node before continuing!')
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleCreateEdgeModalClose = () => {
|
||||
if (isEdgeOneCreated) {
|
||||
setModals((prevModals) => ({ ...prevModals, showCreateEdgeModal: false }));
|
||||
localStorage.setItem("createEdgeModalDismissed", "true");
|
||||
setModals((prevModals) => ({ ...prevModals, showConditionalEdgeModal: true }));
|
||||
setModals((prevModals) => ({ ...prevModals, showCreateEdgeModal: false }))
|
||||
localStorage.setItem('createEdgeModalDismissed', 'true')
|
||||
setModals((prevModals) => ({ ...prevModals, showConditionalEdgeModal: true }))
|
||||
} else {
|
||||
alert("Please create an edge before continuing!");
|
||||
alert('Please create an edge before continuing!')
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleConditionalEdgeModalClose = () => {
|
||||
if (isConditionalEdgeCreated) {
|
||||
setModals((prevModals) => ({ ...prevModals, showConditionalEdgeModal: false }));
|
||||
setModals((prevModals) => ({ ...prevModals, showConditionalEdgeModal: false }))
|
||||
localStorage.setItem('conditionalEdgeModalDismissed', 'true')
|
||||
setModals((prevModals) => ({ ...prevModals, showRenameModal: true }));
|
||||
setModals((prevModals) => ({ ...prevModals, showRenameModal: true }))
|
||||
} else {
|
||||
alert("Please create a conditional edge before continuing!");
|
||||
alert('Please create a conditional edge before continuing!')
|
||||
}
|
||||
}
|
||||
|
||||
const handleRenameModalClose = () => {
|
||||
setModals((prevModals) => ({ ...prevModals, showRenameModal: false }));
|
||||
setModals((prevModals) => ({ ...prevModals, showRenameModal: false }))
|
||||
localStorage.setItem('renameModalDismissed', 'true')
|
||||
setModals((prevModals) => ({ ...prevModals, showGenerateCodeModal: true }));
|
||||
setModals((prevModals) => ({ ...prevModals, showGenerateCodeModal: true }))
|
||||
}
|
||||
|
||||
const handleGenerateCodeModalClose = () => {
|
||||
setModals((prevModals) => ({ ...prevModals, showGenerateCodeModal: false }));
|
||||
setModals((prevModals) => ({ ...prevModals, showGenerateCodeModal: false }))
|
||||
localStorage.setItem('generateCodeModalDismissed', 'true')
|
||||
}
|
||||
|
||||
|
||||
const genericModalArray = [
|
||||
{
|
||||
noClickThrough: true,
|
||||
imageUrl: "/langgraph-logo.png",
|
||||
isOpen: modals.showWelcomeModal,
|
||||
onClose: handleWelcomeModalClose,
|
||||
title: "Graph Builder",
|
||||
content: <span>Use this tool to quickly prototype the architecture of your agent. If you're new to LangGraph, check out our docs <a style={{textDecoration: 'underline'}} href='https://langchain-ai.github.io/langgraph/tutorials/introduction/' target='_blank' rel='noopener noreferrer'>here</a></span>,
|
||||
buttonText: "Get Started",
|
||||
noClickThrough: true,
|
||||
imageUrl: '/langgraph-logo.png',
|
||||
isOpen: modals.showWelcomeModal,
|
||||
onClose: handleWelcomeModalClose,
|
||||
title: 'Graph Builder',
|
||||
content: (
|
||||
<span>
|
||||
Use this tool to quickly prototype the architecture of your agent. If you're new to LangGraph, check out our
|
||||
docs{' '}
|
||||
<a
|
||||
style={{ textDecoration: 'underline' }}
|
||||
href='https://langchain-ai.github.io/langgraph/tutorials/introduction/'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
here
|
||||
</a>
|
||||
</span>
|
||||
),
|
||||
buttonText: 'Get Started',
|
||||
},
|
||||
{
|
||||
hideBackDrop: true,
|
||||
className: 'absolute top-1/2 left-10 transform -translate-y-1/2',
|
||||
isOpen: modals.showCreateNodeModal,
|
||||
onClose: handleCreateNodeModalClose,
|
||||
title: "Create a node",
|
||||
content: "To create a node, click anywhere on the screen. Move a node by clicking and dragging it",
|
||||
buttonText: "Continue",
|
||||
title: 'Create a node',
|
||||
content: 'To create a node, click anywhere on the screen. Move a node by clicking and dragging it',
|
||||
buttonText: 'Continue',
|
||||
},
|
||||
{
|
||||
hideBackDrop: true,
|
||||
className: 'absolute top-10 left-1/2 transform -translate-x-1/2',
|
||||
isOpen: modals.showCreateEdgeModal,
|
||||
onClose: handleCreateEdgeModalClose,
|
||||
title: "Create an edge",
|
||||
content: "To create an edge, click and drag from the top/bottom of one node to another node",
|
||||
buttonText: "Continue",
|
||||
title: 'Create an edge',
|
||||
content: 'To create an edge, click and drag from the top/bottom of one node to another node',
|
||||
buttonText: 'Continue',
|
||||
},
|
||||
{
|
||||
hideBackDrop: true,
|
||||
isOpen: modals.showConditionalEdgeModal,
|
||||
className: 'absolute top-1/2 left-10 transform -translate-y-1/2',
|
||||
onClose: handleConditionalEdgeModalClose,
|
||||
title: "Create a conditional edge",
|
||||
content: "Edges are non-conditional by default. To create a conditional edge, click on a non-conditional edge or draw multiple edges leaving from the same node",
|
||||
buttonText: "Continue",
|
||||
onClose: handleConditionalEdgeModalClose,
|
||||
title: 'Create a conditional edge',
|
||||
content:
|
||||
'Edges are non-conditional by default. To create a conditional edge, click on a non-conditional edge or draw multiple edges leaving from the same node',
|
||||
buttonText: 'Continue',
|
||||
},
|
||||
{
|
||||
hideBackDrop: true,
|
||||
className: 'absolute top-10 left-1/2 transform -translate-x-1/2',
|
||||
isOpen: modals.showRenameModal,
|
||||
onClose: handleRenameModalClose,
|
||||
title: "Delete an edge",
|
||||
content: "Double click quickly on an edge to delete it",
|
||||
buttonText: "Continue",
|
||||
title: 'Delete an edge',
|
||||
content: 'Double click quickly on an edge to delete it',
|
||||
buttonText: 'Continue',
|
||||
},
|
||||
{
|
||||
isOpen: modals.showGenerateCodeModal,
|
||||
onClose: handleGenerateCodeModalClose,
|
||||
title: "Happy building!",
|
||||
content: "Once you're done prototyping, click Generate Code in the bottom right corner to get LangGraph code based on your nodes and edges",
|
||||
buttonText: "Finish",
|
||||
title: 'Happy building!',
|
||||
content:
|
||||
"Once you're done prototyping, click Generate Code in the bottom right corner to get LangGraph code based on your nodes and edges",
|
||||
buttonText: 'Finish',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
const handleEdgeLabelClick = useCallback((sourceNodeId: string) => {
|
||||
setSelectedEdgeId(sourceNodeId)
|
||||
setIsEdgeLabelModalOpen(true)
|
||||
@@ -223,7 +237,7 @@ export default function App() {
|
||||
|
||||
setEdges((prevEdges) => {
|
||||
const updatedEdges = addEdge(newEdge, prevEdges)
|
||||
|
||||
|
||||
// Check if there are other edges from the same source
|
||||
const sourceEdges = updatedEdges.filter((edge) => edge.source === connection.source)
|
||||
|
||||
@@ -246,56 +260,6 @@ export default function App() {
|
||||
[setEdges, edges, buttonTexts, updateEdgeLabel, edgeLabels, maxEdgeLength],
|
||||
)
|
||||
|
||||
const onChange = useCallback(
|
||||
({ nodes, edges }: { nodes: Node[]; edges: Edge[] }) => {
|
||||
console.log('Flow changed:', nodes, edges)
|
||||
if (edges.length == 0) return
|
||||
|
||||
const currentTime = new Date().getTime()
|
||||
if (currentTime - lastClickTime < 300) {
|
||||
// Double-click detected (300ms threshold)
|
||||
setEdges((edgs) =>
|
||||
applyEdgeChanges(
|
||||
[
|
||||
{
|
||||
type: 'remove',
|
||||
id: edges[0].id,
|
||||
},
|
||||
],
|
||||
edgs,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
setEdges((edgs) => {
|
||||
const defaultLabel = `conditional_${buttonTexts[edges[0].source] ? buttonTexts[edges[0].source].replace(/\s+/g, '_') : 'default'}`
|
||||
const label = edgeLabels[edges[0].id] || defaultLabel
|
||||
// updateEdgeLabel(edges[0].id, label)
|
||||
return applyEdgeChanges(
|
||||
[
|
||||
{
|
||||
type: 'replace',
|
||||
id: edges[0].id,
|
||||
item: {
|
||||
...edges[0],
|
||||
source: edges[0].source,
|
||||
target: edges[0].target,
|
||||
animated: !edges[0].animated,
|
||||
selected: false,
|
||||
label: label,
|
||||
},
|
||||
},
|
||||
],
|
||||
edgs,
|
||||
)
|
||||
})
|
||||
}
|
||||
setLastClickTime(currentTime)
|
||||
},
|
||||
[edges, setEdges, lastClickTime],
|
||||
)
|
||||
|
||||
useOnSelectionChange({ onChange })
|
||||
|
||||
const addNode = useCallback(
|
||||
(event: React.MouseEvent) => {
|
||||
if (isConnecting) {
|
||||
@@ -328,9 +292,9 @@ export default function App() {
|
||||
item: newNode,
|
||||
},
|
||||
],
|
||||
prevNodes
|
||||
);
|
||||
});
|
||||
prevNodes,
|
||||
)
|
||||
})
|
||||
}
|
||||
},
|
||||
[nodes, setNodes, reactFlowInstance, reactFlowWrapper, isConnecting, applyNodeChanges, maxNodeLength],
|
||||
@@ -357,22 +321,42 @@ export default function App() {
|
||||
|
||||
const copyCodeToClipboard = () => {
|
||||
if (generatedCode) {
|
||||
navigator.clipboard.writeText(generatedCode.code)
|
||||
navigator.clipboard
|
||||
.writeText(generatedCode.code)
|
||||
.then(() => {
|
||||
alert("Code copied to clipboard!");
|
||||
alert('Code copied to clipboard!')
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to copy code: ', err)
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Failed to copy code: ", err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const onEdgeClick = useCallback(
|
||||
(event: React.MouseEvent, edge: Edge) => {
|
||||
event.stopPropagation()
|
||||
setEdges((eds) => eds.map((e) => (e.id === edge.id ? { ...e, animated: !e.animated } : e)))
|
||||
},
|
||||
[setEdges],
|
||||
)
|
||||
|
||||
const onEdgeDoubleClick = useCallback(
|
||||
(event: React.MouseEvent, edge: Edge) => {
|
||||
console.log('onEdgeDoubleClick', edge)
|
||||
event.stopPropagation()
|
||||
setEdges((eds) => eds.filter((e) => e.id !== edge.id))
|
||||
},
|
||||
[setEdges],
|
||||
)
|
||||
|
||||
console.log(nodes)
|
||||
console.log(edges)
|
||||
|
||||
return (
|
||||
<div ref={reactFlowWrapper} className='z-10 no-scrollbar' style={{ width: '100vw', height: '100vh' }}>
|
||||
<ReactFlow<CustomNodeType, CustomEdgeType>
|
||||
onEdgeClick={onEdgeClick}
|
||||
onEdgeDoubleClick={onEdgeDoubleClick}
|
||||
nodes={nodes}
|
||||
nodeTypes={nodeTypes}
|
||||
onNodesChange={onNodesChange}
|
||||
@@ -402,36 +386,42 @@ export default function App() {
|
||||
>
|
||||
Generate Code
|
||||
</Button>
|
||||
{
|
||||
genericModalArray.map((modal, index) => {
|
||||
return (
|
||||
<GenericModal key={index} {...modal} />
|
||||
)
|
||||
})
|
||||
}
|
||||
{genericModalArray.map((modal, index) => {
|
||||
return <GenericModal key={index} {...modal} />
|
||||
})}
|
||||
{showModal && <Modal onClose={() => setShowModal(false)} onSelect={handleCodeTypeSelection} />}
|
||||
<MuiModal
|
||||
hideBackdrop={false}
|
||||
onClose={() => {
|
||||
setGenerateCodeModalOpen(false);
|
||||
}}
|
||||
open={generateCodeModalOpen}>
|
||||
<ModalDialog className="bg-slate-150">
|
||||
<div className='flex justify-center items-center h-full'>
|
||||
<div className='bg-slate-100 px-5 py-6 rounded-lg flex flex-col justify-center'>
|
||||
<h3 className='text-lg font-bold mb-2'>Generated Code:</h3>
|
||||
<pre className='bg-gray-100 px-3 rounded my-5'>
|
||||
<code>{generatedCode?.code}</code>
|
||||
</pre>
|
||||
<div className='flex flex-row justify-center'>
|
||||
<Button className='bg-[#246161] hover:bg-[#195656] text-white font-bold px-2 rounded w-32' onClick={copyCodeToClipboard}>
|
||||
Copy Code
|
||||
</Button>
|
||||
hideBackdrop={false}
|
||||
onClose={() => {
|
||||
setGenerateCodeModalOpen(false)
|
||||
}}
|
||||
open={generateCodeModalOpen}
|
||||
>
|
||||
<ModalDialog className='bg-slate-150'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<h2 className='text-lg font-bold'>Generated Code:</h2>
|
||||
<Button
|
||||
className='bg-[#246161] hover:bg-[#195656] text-white font-bold px-2 rounded w-28'
|
||||
onClick={copyCodeToClipboard}
|
||||
>
|
||||
Copy Code
|
||||
</Button>
|
||||
</div>
|
||||
<div className='overflow-y-scroll overflow-x-scroll justify-center'>
|
||||
<pre className='py-6 px-3'>
|
||||
<code>{generatedCode?.code}</code>
|
||||
</pre>
|
||||
</div>
|
||||
<div className='flex justify-center'>
|
||||
<Button
|
||||
className='bg-[#FF7F7F] hover:bg-[#FF5C5C] text-white font-bold px-2 rounded w-20'
|
||||
onClick={() => setGenerateCodeModalOpen(false)}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalDialog>
|
||||
</MuiModal>
|
||||
</MuiModal>
|
||||
<EdgeLabelModal
|
||||
isOpen={isEdgeLabelModalOpen}
|
||||
onClose={() => setIsEdgeLabelModalOpen(false)}
|
||||
|
||||
@@ -1,18 +1,54 @@
|
||||
import React from 'react'
|
||||
import { BaseEdge, EdgeProps, getBezierPath, EdgeText } from '@xyflow/react'
|
||||
import React, { useState } from 'react'
|
||||
import { BaseEdge, EdgeProps, getBezierPath } from '@xyflow/react'
|
||||
import { useEdgeLabel } from '@/contexts/EdgeLabelContext'
|
||||
import { useButtonText } from '@/contexts/ButtonTextContext'
|
||||
|
||||
interface SelfConnectingEdgeProps extends EdgeProps {
|
||||
data?: {
|
||||
onLabelClick: (id: string) => void
|
||||
updateEdgeLabel: (id: string, newLabel: string) => void
|
||||
}
|
||||
}
|
||||
|
||||
export default function SelfConnectingEdge(props: SelfConnectingEdgeProps) {
|
||||
const { sourceX, sourceY, targetX, targetY, id, markerEnd, label, animated, source } = props
|
||||
const { edgeLabels } = useEdgeLabel()
|
||||
const { edgeLabels, updateEdgeLabel } = useEdgeLabel()
|
||||
const { buttonTexts } = useButtonText()
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
const [currentLabel, setCurrentLabel] = useState(
|
||||
edgeLabels[source] || `conditional_${buttonTexts[source]?.replaceAll(' ', '_')}` || (label as string),
|
||||
)
|
||||
|
||||
const handleLabelClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
setIsEditing(true)
|
||||
}
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
e.stopPropagation()
|
||||
setCurrentLabel(e.target.value)
|
||||
}
|
||||
|
||||
const handleInputBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
e.stopPropagation()
|
||||
updateEdgeLabel(source, currentLabel)
|
||||
setIsEditing(false)
|
||||
}
|
||||
|
||||
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
e.stopPropagation()
|
||||
if (e.key === 'Enter') {
|
||||
updateEdgeLabel(source, currentLabel)
|
||||
setIsEditing(false)
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
setCurrentLabel(
|
||||
edgeLabels[source] || `conditional_${buttonTexts[source]?.replaceAll(' ', '_')}` || (label as string),
|
||||
)
|
||||
setIsEditing(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (props.source !== props.target) {
|
||||
const [edgePath, labelX, labelY] = getBezierPath({
|
||||
sourceX,
|
||||
@@ -38,23 +74,38 @@ export default function SelfConnectingEdge(props: SelfConnectingEdgeProps) {
|
||||
</marker>
|
||||
</defs>
|
||||
<BaseEdge {...props} id={id} path={edgePath} markerEnd={'url(#triangle)'} />
|
||||
{label && animated && (
|
||||
<EdgeText
|
||||
x={labelX}
|
||||
y={labelY}
|
||||
label={
|
||||
edgeLabels[source] || `conditional_${buttonTexts[source]?.replaceAll(' ', '_')}` || (label as string)
|
||||
}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
props.data?.onLabelClick(id)
|
||||
}}
|
||||
|
||||
labelBgPadding={[10, 10]}
|
||||
labelBgStyle={{ fill: '#2596be', stroke: '#207fa5', strokeWidth: 2 }}
|
||||
labelStyle={{ fill: '#f5f5dc', fontSize: 10, fontWeight: 'medium', textAlign: 'center', flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }}
|
||||
/>
|
||||
)}
|
||||
{label &&
|
||||
animated &&
|
||||
(isEditing ? (
|
||||
<foreignObject className='pointer-events-none' x={labelX - 70} y={labelY - 10} width={160} height={35}>
|
||||
<input
|
||||
data-stop-propagation='true'
|
||||
type='text'
|
||||
value={currentLabel}
|
||||
onChange={handleInputChange}
|
||||
onBlur={handleInputBlur}
|
||||
autoFocus
|
||||
onKeyDown={(e) => {
|
||||
e.stopPropagation()
|
||||
handleInputKeyDown(e)
|
||||
}}
|
||||
className='cursor-none bg-[#2596be] pointer-events-none outline-none border border-2 border-[#207fa5] text-center text-white w-full h-full text-xs text-white rounded'
|
||||
/>
|
||||
</foreignObject>
|
||||
) : (
|
||||
<foreignObject x={labelX - 70} y={labelY - 10} width={160} height={35}>
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleLabelClick(e)
|
||||
}}
|
||||
data-stop-propagation='true'
|
||||
className='bg-[#2596be] border border-2 border-[#207fa5] flex justify-center items-center flex text-center text-white w-full h-full text-xs text-white rounded'
|
||||
>
|
||||
{currentLabel}
|
||||
</div>
|
||||
</foreignObject>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -64,26 +115,38 @@ export default function SelfConnectingEdge(props: SelfConnectingEdgeProps) {
|
||||
return (
|
||||
<>
|
||||
<BaseEdge path={edgePath} markerEnd={markerEnd} />
|
||||
{label && (
|
||||
<EdgeText
|
||||
x={sourceX + 100}
|
||||
y={sourceY - 70}
|
||||
label={edgeLabels[source] || `conditional_${buttonTexts[source]?.replaceAll(' ', '_')}` || (label as string)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
props.data?.onLabelClick(id)
|
||||
}}
|
||||
style={{
|
||||
background: 'transparent',
|
||||
border: 'none',
|
||||
outline: 'none',
|
||||
padding: 8,
|
||||
margin: 0,
|
||||
zIndex: 1000,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{label &&
|
||||
animated &&
|
||||
(isEditing ? (
|
||||
<foreignObject x={sourceX + 30} y={sourceY + 5} width={150} height={35}>
|
||||
<input
|
||||
type='text'
|
||||
value={currentLabel}
|
||||
onChange={handleInputChange}
|
||||
onBlur={handleInputBlur}
|
||||
onKeyDown={(e) => {
|
||||
e.stopPropagation()
|
||||
handleInputKeyDown(e)
|
||||
}}
|
||||
autoFocus
|
||||
data-stop-propagation='true'
|
||||
className='cursor-none bg-[#2596be] pointer-events-none outline-none border border-2 border-[#207fa5] text-center text-white w-full h-full text-xs text-white rounded'
|
||||
/>
|
||||
</foreignObject>
|
||||
) : (
|
||||
<foreignObject x={sourceX + 30} y={sourceY + 5} width={150} height={35}>
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleLabelClick(e)
|
||||
}}
|
||||
data-stop-propagation='true'
|
||||
className='bg-[#2596be] border border-2 border-[#207fa5] flex justify-center items-center flex text-center text-white w-full h-full text-xs text-white rounded'
|
||||
>
|
||||
<div className='px-2'>{currentLabel}</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user