mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-14 17:37:55 -05:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e8c843241 | ||
|
|
7bf3d73ee6 | ||
|
|
7ffc11a738 | ||
|
|
be578e2ed7 | ||
|
|
f415e5edc4 | ||
|
|
13a6e6c3fa | ||
|
|
f5ab7f21ae | ||
|
|
bfb6fffe38 | ||
|
|
4fbec0a43f | ||
|
|
585f5e365b | ||
|
|
3792bdd252 | ||
|
|
eb5d1f3e5b | ||
|
|
54ab82c8dd | ||
|
|
f895bf469b | ||
|
|
dd3209af06 | ||
|
|
b6ba3b50a7 | ||
|
|
b304233062 | ||
|
|
57e4b49bd6 | ||
|
|
e12dd204ed | ||
|
|
3d9d9cbc54 | ||
|
|
0f4ec962ad | ||
|
|
4827866f9a | ||
|
|
3e697d9ed9 | ||
|
|
4431a1a484 | ||
|
|
4d1a9a3f22 | ||
|
|
eb07a080fb |
@@ -356,9 +356,6 @@ const WorkflowContent = React.memo(() => {
|
|||||||
/** Stores source node/handle info when a connection drag starts for drop-on-block detection. */
|
/** Stores source node/handle info when a connection drag starts for drop-on-block detection. */
|
||||||
const connectionSourceRef = useRef<{ nodeId: string; handleId: string } | null>(null)
|
const connectionSourceRef = useRef<{ nodeId: string; handleId: string } | null>(null)
|
||||||
|
|
||||||
/** Tracks whether onConnect successfully handled the connection (ReactFlow pattern). */
|
|
||||||
const connectionCompletedRef = useRef(false)
|
|
||||||
|
|
||||||
/** Stores start positions for multi-node drag undo/redo recording. */
|
/** Stores start positions for multi-node drag undo/redo recording. */
|
||||||
const multiNodeDragStartRef = useRef<Map<string, { x: number; y: number; parentId?: string }>>(
|
const multiNodeDragStartRef = useRef<Map<string, { x: number; y: number; parentId?: string }>>(
|
||||||
new Map()
|
new Map()
|
||||||
@@ -2217,8 +2214,7 @@ const WorkflowContent = React.memo(() => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Captures the source handle when a connection drag starts.
|
* Captures the source handle when a connection drag starts
|
||||||
* Resets connectionCompletedRef to track if onConnect handles this connection.
|
|
||||||
*/
|
*/
|
||||||
const onConnectStart = useCallback((_event: any, params: any) => {
|
const onConnectStart = useCallback((_event: any, params: any) => {
|
||||||
const handleId: string | undefined = params?.handleId
|
const handleId: string | undefined = params?.handleId
|
||||||
@@ -2227,7 +2223,6 @@ const WorkflowContent = React.memo(() => {
|
|||||||
nodeId: params?.nodeId,
|
nodeId: params?.nodeId,
|
||||||
handleId: params?.handleId,
|
handleId: params?.handleId,
|
||||||
}
|
}
|
||||||
connectionCompletedRef.current = false
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
/** Handles new edge connections with container boundary validation. */
|
/** Handles new edge connections with container boundary validation. */
|
||||||
@@ -2288,7 +2283,6 @@ const WorkflowContent = React.memo(() => {
|
|||||||
isInsideContainer: true,
|
isInsideContainer: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
connectionCompletedRef.current = true
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2317,7 +2311,6 @@ const WorkflowContent = React.memo(() => {
|
|||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
})
|
})
|
||||||
connectionCompletedRef.current = true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[addEdge, getNodes, blocks]
|
[addEdge, getNodes, blocks]
|
||||||
@@ -2326,9 +2319,8 @@ const WorkflowContent = React.memo(() => {
|
|||||||
/**
|
/**
|
||||||
* Handles connection drag end. Detects if the edge was dropped over a block
|
* Handles connection drag end. Detects if the edge was dropped over a block
|
||||||
* and automatically creates a connection to that block's target handle.
|
* and automatically creates a connection to that block's target handle.
|
||||||
*
|
* Only creates a connection if ReactFlow didn't already handle it (e.g., when
|
||||||
* Uses connectionCompletedRef to check if onConnect already handled this connection
|
* dropping on the block body instead of a handle).
|
||||||
* (ReactFlow pattern for distinguishing handle-to-handle vs handle-to-body drops).
|
|
||||||
*/
|
*/
|
||||||
const onConnectEnd = useCallback(
|
const onConnectEnd = useCallback(
|
||||||
(event: MouseEvent | TouchEvent) => {
|
(event: MouseEvent | TouchEvent) => {
|
||||||
@@ -2340,12 +2332,6 @@ const WorkflowContent = React.memo(() => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If onConnect already handled this connection, skip (handle-to-handle case)
|
|
||||||
if (connectionCompletedRef.current) {
|
|
||||||
connectionSourceRef.current = null
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get cursor position in flow coordinates
|
// Get cursor position in flow coordinates
|
||||||
const clientPos = 'changedTouches' in event ? event.changedTouches[0] : event
|
const clientPos = 'changedTouches' in event ? event.changedTouches[0] : event
|
||||||
const flowPosition = screenToFlowPosition({
|
const flowPosition = screenToFlowPosition({
|
||||||
@@ -2356,7 +2342,12 @@ const WorkflowContent = React.memo(() => {
|
|||||||
// Find node under cursor
|
// Find node under cursor
|
||||||
const targetNode = findNodeAtPosition(flowPosition)
|
const targetNode = findNodeAtPosition(flowPosition)
|
||||||
|
|
||||||
// Create connection if valid target found (handle-to-body case)
|
// Create connection if valid target found AND edge doesn't already exist
|
||||||
|
// ReactFlow's onConnect fires first when dropping on a handle, so we check
|
||||||
|
// if that connection already exists to avoid creating duplicates.
|
||||||
|
// IMPORTANT: We must read directly from the store (not React state) because
|
||||||
|
// the store update from ReactFlow's onConnect may not have triggered a
|
||||||
|
// React re-render yet when this callback runs (typically 1-2ms later).
|
||||||
if (targetNode && targetNode.id !== source.nodeId) {
|
if (targetNode && targetNode.id !== source.nodeId) {
|
||||||
const currentEdges = useWorkflowStore.getState().edges
|
const currentEdges = useWorkflowStore.getState().edges
|
||||||
const edgeAlreadyExists = currentEdges.some(
|
const edgeAlreadyExists = currentEdges.some(
|
||||||
|
|||||||
@@ -24,9 +24,7 @@ export function hasWorkflowChanged(
|
|||||||
deployedState: WorkflowState | null
|
deployedState: WorkflowState | null
|
||||||
): boolean {
|
): boolean {
|
||||||
// If no deployed state exists, then the workflow has changed
|
// If no deployed state exists, then the workflow has changed
|
||||||
if (!deployedState) {
|
if (!deployedState) return true
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Compare edges (connections between blocks)
|
// 1. Compare edges (connections between blocks)
|
||||||
const currentEdges = currentState.edges || []
|
const currentEdges = currentState.edges || []
|
||||||
|
|||||||
@@ -197,10 +197,9 @@ export function normalizeEdge(edge: Edge): NormalizedEdge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sorts and deduplicates edges for consistent comparison.
|
* Sorts edges for consistent comparison
|
||||||
* Deduplication handles legacy data that may contain duplicate edges.
|
|
||||||
* @param edges - Array of edges to sort
|
* @param edges - Array of edges to sort
|
||||||
* @returns Sorted array of unique normalized edges
|
* @returns Sorted array of normalized edges
|
||||||
*/
|
*/
|
||||||
export function sortEdges(
|
export function sortEdges(
|
||||||
edges: Array<{
|
edges: Array<{
|
||||||
@@ -215,13 +214,7 @@ export function sortEdges(
|
|||||||
target: string
|
target: string
|
||||||
targetHandle?: string | null
|
targetHandle?: string | null
|
||||||
}> {
|
}> {
|
||||||
const uniqueEdges = new Map<string, (typeof edges)[number]>()
|
return [...edges].sort((a, b) =>
|
||||||
for (const edge of edges) {
|
|
||||||
const key = `${edge.source}-${edge.sourceHandle ?? 'null'}-${edge.target}-${edge.targetHandle ?? 'null'}`
|
|
||||||
uniqueEdges.set(key, edge)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from(uniqueEdges.values()).sort((a, b) =>
|
|
||||||
`${a.source}-${a.sourceHandle}-${a.target}-${a.targetHandle}`.localeCompare(
|
`${a.source}-${a.sourceHandle}-${a.target}-${a.targetHandle}`.localeCompare(
|
||||||
`${b.source}-${b.sourceHandle}-${b.target}-${b.targetHandle}`
|
`${b.source}-${b.sourceHandle}-${b.target}-${b.targetHandle}`
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -498,6 +498,8 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
|||||||
const currentEdges = get().edges
|
const currentEdges = get().edges
|
||||||
const newEdges = [...currentEdges]
|
const newEdges = [...currentEdges]
|
||||||
const existingEdgeIds = new Set(currentEdges.map((e) => e.id))
|
const existingEdgeIds = new Set(currentEdges.map((e) => e.id))
|
||||||
|
// Track existing connections to prevent duplicates (same source->target)
|
||||||
|
const existingConnections = new Set(currentEdges.map((e) => `${e.source}->${e.target}`))
|
||||||
|
|
||||||
for (const edge of edges) {
|
for (const edge of edges) {
|
||||||
// Skip if edge ID already exists
|
// Skip if edge ID already exists
|
||||||
@@ -506,6 +508,10 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
|||||||
// Skip self-referencing edges
|
// Skip self-referencing edges
|
||||||
if (edge.source === edge.target) continue
|
if (edge.source === edge.target) continue
|
||||||
|
|
||||||
|
// Skip if connection already exists (same source and target)
|
||||||
|
const connectionKey = `${edge.source}->${edge.target}`
|
||||||
|
if (existingConnections.has(connectionKey)) continue
|
||||||
|
|
||||||
// Skip if would create a cycle
|
// Skip if would create a cycle
|
||||||
if (wouldCreateCycle([...newEdges], edge.source, edge.target)) continue
|
if (wouldCreateCycle([...newEdges], edge.source, edge.target)) continue
|
||||||
|
|
||||||
@@ -519,6 +525,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
|||||||
data: edge.data || {},
|
data: edge.data || {},
|
||||||
})
|
})
|
||||||
existingEdgeIds.add(edge.id)
|
existingEdgeIds.add(edge.id)
|
||||||
|
existingConnections.add(connectionKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
const blocks = get().blocks
|
const blocks = get().blocks
|
||||||
|
|||||||
Reference in New Issue
Block a user