diff --git a/components/process-flow/ProcessFlowCanvas.vue b/components/process-flow/ProcessFlowCanvas.vue index 51edea1..3aeb1fd 100644 --- a/components/process-flow/ProcessFlowCanvas.vue +++ b/components/process-flow/ProcessFlowCanvas.vue @@ -89,6 +89,8 @@ const emit = defineEmits([ "edgesChange", "nodeSelected", "edgeSelected", + "selectionChange", + "nodesSelection", ]); // Get the flow instance @@ -121,8 +123,6 @@ const { markerEnd: "url(#arrow)", }, deleteKeyCode: "Delete", - selectionKeyCode: "Shift", - multiSelectionKeyCode: "Control", connectionMode: "strict", edgeUpdaterRadius: 12, edgesUpdatable: true, @@ -130,6 +130,10 @@ const { isValidConnection: (connection) => { return true; }, + selectNodesOnDrag: true, + panOnDrag: [1, 2], + panOnScroll: false, + zoomOnScroll: true, }); // Define custom edge types - use markRaw to prevent reactivity issues @@ -158,18 +162,32 @@ const flowOptions = ref({ nodesDraggable: true, nodesConnectable: true, elementsSelectable: true, - selectNodesOnDrag: false, - panOnDrag: [0, 2], + selectNodesOnDrag: true, + panOnDrag: [1, 2], panOnScroll: false, zoomOnScroll: true, zoomOnPinch: true, zoomOnDoubleClick: false, connectOnClick: false, + selectionMode: "partial", + selectionKeyCode: true, + multiSelectionKeyCode: true, + selectionOnDrag: true, + selectionBoxMode: "Default", + preventScrolling: true, }); // Use shallowRef for selected node to avoid unnecessary reactivity const selectedNode = shallowRef(null); +// Help guide state +const showHelpGuide = ref(false); + +// Toggle help guide +const toggleHelpGuide = () => { + showHelpGuide.value = !showHelpGuide.value; +}; + // State management for preventing recursive updates const isUpdatingNodes = ref(false); const isUpdatingEdges = ref(false); @@ -201,10 +219,23 @@ const onNodeClick = ({ node }) => { selectedNode.value = nodeData; emit("nodeSelected", nodeData); } catch (error) { - console.error("Error processing node data:", error); + console.error("Error processing node click:", error); } }; +// Handle selection change +const onSelectionChange = ({ nodes, edges }) => { + console.log('Selection changed:', { nodes: nodes.length, edges: edges.length }); + // You can add custom logic here for handling multiple selections +}; + +// Handle multiple node selection +const onNodesSelection = ({ nodes }) => { + console.log('Multiple nodes selected:', nodes.map(n => n.id)); + // Emit event for multiple node selection + emit("nodeSelected", nodes); +}; + // Handle edge click const onEdgeClick = (event, edge) => { // Handle different parameter formats Vue Flow might send @@ -1424,6 +1455,8 @@ function fromObject(flowObject) { @nodeDoubleClick="onNodeDelete" @edgeDoubleClick="onEdgeDelete" @keyup.delete="onDeleteKeyPress" + @selectionChange="onSelectionChange" + @nodesSelection="onNodesSelection" > @@ -1476,18 +1509,49 @@ function fromObject(flowObject) {
{{ label }}
- -
-
Controls:
-
• Delete: Remove selected
-
• Shift: Select nodes
-
• Drag between nodes to connect
-
• Double-click to remove
-
Edge Features:
-
• Arrows show flow direction
-
• Select edge for controls
-
• Blue dot = drag to reposition
-
• Reset button restores path
+ + + +
+ + +
+
+

Selection

+
    +
  • • Click: Select single node
  • +
  • • Drag: Select multiple nodes
  • +
  • • Ctrl+Click: Add to selection
  • +
  • • Drag selected nodes together
  • +
+
+ +
+

Actions

+
    +
  • • Delete: Remove selected
  • +
  • • Double-click: Remove node/edge
  • +
  • • Drag between nodes: Connect
  • +
+
+ +
+

Edges

+
    +
  • • Select edge for controls
  • +
  • • Blue dot: Reposition
  • +
  • • Reset: Restore path
  • +
+
+
@@ -1691,12 +1755,7 @@ function fromObject(flowObject) { } .node-controls { - margin: 10px; - color: #666; - font-size: 12px; - background: white; - border-radius: 4px; - pointer-events: all; + display: none; } :deep(.vue-flow__handle) { @@ -1758,4 +1817,126 @@ function fromObject(flowObject) { 0 0 30px rgba(96, 165, 250, 0.6); } } + +/* Selection box styles */ +:deep(.vue-flow__selection) { + background: rgba(59, 130, 246, 0.1); + border: 2px solid #3b82f6; + border-radius: 4px; + pointer-events: none; +} + +:deep(.vue-flow__selection-rect) { + fill: rgba(59, 130, 246, 0.1); + stroke: #3b82f6; + stroke-width: 2; + stroke-dasharray: 5, 5; +} + +/* Multi-selected nodes styling */ +:deep(.vue-flow__node.selected) { + box-shadow: 0 0 0 2px #3b82f6; + z-index: 10; +} + +:deep(.vue-flow__node.selected .custom-node) { + box-shadow: 0 0 0 2px #3b82f6, 0 2px 5px rgba(0, 0, 0, 0.1); +} + +/* Selection box animation */ +@keyframes selectionPulse { + 0%, 100% { + opacity: 0.3; + } + 50% { + opacity: 0.6; + } +} + +:deep(.vue-flow__selection) { + animation: selectionPulse 2s ease-in-out infinite; +} + +/* Help Guide Styles */ +.help-guide { + pointer-events: all; +} + +.help-guide-content { + background: white; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + overflow: hidden; + max-width: 280px; + transition: all 0.3s ease; +} + +.help-toggle-btn { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + background: #3b82f6; + color: white; + border: none; + border-radius: 6px; + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; +} + +.help-toggle-btn:hover { + background: #2563eb; + transform: translateY(-1px); +} + +.help-toggle-btn.expanded { + background: #dc2626; +} + +.help-toggle-btn.expanded:hover { + background: #b91c1c; +} + +.help-content { + padding: 12px; + background: white; + border-top: 1px solid #e5e7eb; +} + +.help-section { + margin-bottom: 12px; +} + +.help-section:last-child { + margin-bottom: 0; +} + +.help-title { + font-size: 11px; + font-weight: 600; + color: #374151; + margin-bottom: 6px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.help-list { + list-style: none; + padding: 0; + margin: 0; +} + +.help-list li { + font-size: 11px; + color: #6b7280; + margin-bottom: 3px; + line-height: 1.3; +} + +.help-list li:last-child { + margin-bottom: 0; +}