|
1 | 1 | import React, { useRef, useState, forwardRef, useImperativeHandle, useEffect } from 'react'; |
2 | 2 | import ReactFlow, { |
3 | 3 | MiniMap, |
4 | | - Controls, |
5 | 4 | Background, |
6 | 5 | ReactFlowInstance, |
7 | 6 | Node, |
@@ -54,6 +53,7 @@ export const FlowCanvas = forwardRef<{ |
54 | 53 | const reactFlowWrapper = useRef<HTMLDivElement>(null); |
55 | 54 | const [reactFlowInstance, setReactFlowInstance] = useState<ReactFlowInstance | null>(null); |
56 | 55 | const [isDragOver, setIsDragOver] = useState(false); |
| 56 | + const [isInteractive, setIsInteractive] = useState(true); |
57 | 57 | const [selectedFileId, setSelectedFileId] = useState<string | null>(null); |
58 | 58 | const [expandedFileId, setExpandedFileId] = useState<string | null>(null); |
59 | 59 |
|
@@ -350,9 +350,15 @@ export const FlowCanvas = forwardRef<{ |
350 | 350 | nodeTypes={nodeTypes} |
351 | 351 | edgeTypes={edgeTypes} |
352 | 352 | onInit={setReactFlowInstance} |
| 353 | + nodesDraggable={isInteractive} |
| 354 | + nodesConnectable={isInteractive} |
| 355 | + elementsSelectable={isInteractive} |
| 356 | + panOnDrag={isInteractive} |
| 357 | + panOnScroll={isInteractive} |
| 358 | + zoomOnScroll={isInteractive} |
| 359 | + zoomOnPinch={isInteractive} |
353 | 360 | > |
354 | 361 | <MiniMap position="bottom-right" style={{ bottom: 16, right: 16, zIndex: 30 }} /> |
355 | | - <Controls position="bottom-left" /> |
356 | 362 | <Background /> |
357 | 363 | </ReactFlow> |
358 | 364 |
|
@@ -384,6 +390,41 @@ export const FlowCanvas = forwardRef<{ |
384 | 390 | /> |
385 | 391 | ))} |
386 | 392 |
|
| 393 | + {/* Canvas Controls anchored to wrapper so they move with sidebar resizing */} |
| 394 | + <div style={{ position: 'absolute', left: 16, bottom: 16, zIndex: 31, display: 'flex', flexDirection: 'column', gap: 6 }}> |
| 395 | + <button |
| 396 | + onClick={() => reactFlowInstance?.zoomIn?.()} |
| 397 | + style={{ width: 36, height: 36, borderRadius: 6, border: '1px solid #e5e7eb', background: '#ffffff', cursor: 'pointer' }} |
| 398 | + title="Zoom in" |
| 399 | + > |
| 400 | + + |
| 401 | + </button> |
| 402 | + <button |
| 403 | + onClick={() => reactFlowInstance?.zoomOut?.()} |
| 404 | + style={{ width: 36, height: 36, borderRadius: 6, border: '1px solid #e5e7eb', background: '#ffffff', cursor: 'pointer' }} |
| 405 | + title="Zoom out" |
| 406 | + > |
| 407 | + – |
| 408 | + </button> |
| 409 | + <button |
| 410 | + onClick={() => reactFlowInstance?.fitView?.({ padding: 0.2 })} |
| 411 | + style={{ width: 36, height: 36, borderRadius: 6, border: '1px solid #e5e7eb', background: '#ffffff', cursor: 'pointer' }} |
| 412 | + title="Fit view" |
| 413 | + > |
| 414 | + ⛶ |
| 415 | + </button> |
| 416 | + <button |
| 417 | + onClick={() => { |
| 418 | + const next = !isInteractive; |
| 419 | + setIsInteractive(next); |
| 420 | + }} |
| 421 | + style={{ width: 36, height: 36, borderRadius: 6, border: '1px solid #e5e7eb', background: '#ffffff', cursor: 'pointer' }} |
| 422 | + title={isInteractive ? 'Lock interactions' : 'Unlock interactions'} |
| 423 | + > |
| 424 | + {isInteractive ? '🔓' : '🔒'} |
| 425 | + </button> |
| 426 | + </div> |
| 427 | + |
387 | 428 | {isDragOver && ( |
388 | 429 | <div style={{ |
389 | 430 | position: 'absolute', |
|
0 commit comments