Skip to content

Fix: Preserve node positions when dragging UI elements during gateway activity #18

@dasilva333

Description

@dasilva333

Issue:

The UI layout was resetting every time there was activity on the gateway, causing all dragged nodes to return to their default positions. Additionally, there was a "Cannot access 'setNodes' before initialization" error due to a circular dependency.

Changes Made:

  1. Fixed initialization order: Used the standard useNodesState hook first, then wrapped the onNodesChange callback to track user adjustments
  2. Preserved user-adjusted positions: The code now properly maintains node positions when users drag them around
  3. Maintained existing functionality: All other features like the chaser crab and real-time updates continue to work
    How it works now:

• When you drag nodes to arrange them in the UI, those positions are remembered in a ref
• When new activity comes in from the gateway, existing nodes keep their user-adjusted positions
• Only newly added nodes get the default layout positions
• The chaser crab and other special elements continue to work as before

Code Changes:

// Store user-adjusted positions separately
const userNodePositionsRef = useRef<Map<string, { x: number; y: number }>>(new Map())

// Wrap onNodesChange to track user adjustments
const wrappedOnNodesChange = useCallback((changes: any[]) => {
  // Process changes to track manual position adjustments
  for (const change of changes) {
    if (change.type === 'position' && change.dragging === false) {
      // User finished dragging a node - save the new position
      userNodePositionsRef.current.set(change.id, change.position)
    } else if (change.type === 'position' && change.dragging === true) {
      // During dragging, temporarily update the stored position
      if (change.position) {
        userNodePositionsRef.current.set(change.id, change.position)
      }
    }
  }
  // Apply the changes using the original onNodesChange
  onNodesChange(changes)
}, [onNodesChange])
// Update layout nodes when they change (preserve chaser and user-adjusted positions)
useEffect(() => {
  setNodes((nds) => {
    // Get user-adjusted positions
    const userAdjustedPositions = userNodePositionsRef.current

    // Update nodes with preserved positions for existing nodes
    const updatedNodes = layoutedNodes.map((layoutNode) => {
      // Prioritize user-adjusted positions, fall back to existing if not user-adjusted
      const userPosition = userAdjustedPositions.get(layoutNode.id)
      if (userPosition) {
        return { ...layoutNode, position: userPosition }
      }
      // Look for existing position in current nodes (if it existed before)
      const existingNode = nds.find(n => n.id === layoutNode.id)
      if (existingNode && existingNode.id !== CHASER_CRAB_ID) {
        return { ...layoutNode, position: existingNode.position }
      }
      return layoutNode
    })

    // Find and preserve chaser node
    const chaserNode = nds.find((n) => n.id === CHASER_CRAB_ID)
    if (chaserNode) {
      return [...updatedNodes, chaserNode]
    }
    
    const crab = crabRef.current
    return [
      ...updatedNodes,
      {
        id: CHASER_CRAB_ID,
        type: 'chaserCrab',
        position: crab.position,
        data: {
          state: crab.state,
          facingLeft: crab.facingLeft,
          onClick: handleCrabClick,
        },
        draggable: false,
        selectable: false,
        zIndex: 1000,
      },
    ]
  })
  setEdges(layoutedEdges)
}, [layoutedNodes, layoutedEdges, setNodes, setEdges, handleCrabClick])
return (
  <div className="w-full h-full bg-shell-950 texture-grid relative">
    <ReactFlow
      nodes={nodes}
      edges={edges}
      onNodesChange={wrappedOnNodesChange}  // Use the wrapped handler
      onEdgesChange={onEdgesChange}
      onNodeClick={onNodeClick}
      nodeTypes={nodeTypes}
      fitView
      fitViewOptions={{ padding: 0.2 }}
      minZoom={0.1}
      maxZoom={2}
      proOptions={{ hideAttribution: true }}
    >

These changes ensure that the UI layout remains stable and preserves user positioning preferences even when new data flows in from the gateway.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions