Enhance ProcessFlowCanvas with Selection Features and Help Guide

- Added support for node selection changes and multiple node selection events, improving user interaction within the process flow.
- Introduced a collapsible help guide to assist users with selection and action instructions, enhancing usability.
- Updated styles for selection boxes and multi-selected nodes to improve visual feedback during interactions.
- Refactored event handling for node and edge clicks to streamline the selection process and improve error logging.
This commit is contained in:
Md Afiq Iskandar 2025-07-23 07:23:07 +08:00
parent 5261bf601e
commit 80038e00a3

View File

@ -89,6 +89,8 @@ const emit = defineEmits([
"edgesChange", "edgesChange",
"nodeSelected", "nodeSelected",
"edgeSelected", "edgeSelected",
"selectionChange",
"nodesSelection",
]); ]);
// Get the flow instance // Get the flow instance
@ -121,8 +123,6 @@ const {
markerEnd: "url(#arrow)", markerEnd: "url(#arrow)",
}, },
deleteKeyCode: "Delete", deleteKeyCode: "Delete",
selectionKeyCode: "Shift",
multiSelectionKeyCode: "Control",
connectionMode: "strict", connectionMode: "strict",
edgeUpdaterRadius: 12, edgeUpdaterRadius: 12,
edgesUpdatable: true, edgesUpdatable: true,
@ -130,6 +130,10 @@ const {
isValidConnection: (connection) => { isValidConnection: (connection) => {
return true; return true;
}, },
selectNodesOnDrag: true,
panOnDrag: [1, 2],
panOnScroll: false,
zoomOnScroll: true,
}); });
// Define custom edge types - use markRaw to prevent reactivity issues // Define custom edge types - use markRaw to prevent reactivity issues
@ -158,18 +162,32 @@ const flowOptions = ref({
nodesDraggable: true, nodesDraggable: true,
nodesConnectable: true, nodesConnectable: true,
elementsSelectable: true, elementsSelectable: true,
selectNodesOnDrag: false, selectNodesOnDrag: true,
panOnDrag: [0, 2], panOnDrag: [1, 2],
panOnScroll: false, panOnScroll: false,
zoomOnScroll: true, zoomOnScroll: true,
zoomOnPinch: true, zoomOnPinch: true,
zoomOnDoubleClick: false, zoomOnDoubleClick: false,
connectOnClick: false, connectOnClick: false,
selectionMode: "partial",
selectionKeyCode: true,
multiSelectionKeyCode: true,
selectionOnDrag: true,
selectionBoxMode: "Default",
preventScrolling: true,
}); });
// Use shallowRef for selected node to avoid unnecessary reactivity // Use shallowRef for selected node to avoid unnecessary reactivity
const selectedNode = shallowRef(null); 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 // State management for preventing recursive updates
const isUpdatingNodes = ref(false); const isUpdatingNodes = ref(false);
const isUpdatingEdges = ref(false); const isUpdatingEdges = ref(false);
@ -201,10 +219,23 @@ const onNodeClick = ({ node }) => {
selectedNode.value = nodeData; selectedNode.value = nodeData;
emit("nodeSelected", nodeData); emit("nodeSelected", nodeData);
} catch (error) { } 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 // Handle edge click
const onEdgeClick = (event, edge) => { const onEdgeClick = (event, edge) => {
// Handle different parameter formats Vue Flow might send // Handle different parameter formats Vue Flow might send
@ -1424,6 +1455,8 @@ function fromObject(flowObject) {
@nodeDoubleClick="onNodeDelete" @nodeDoubleClick="onNodeDelete"
@edgeDoubleClick="onEdgeDelete" @edgeDoubleClick="onEdgeDelete"
@keyup.delete="onDeleteKeyPress" @keyup.delete="onDeleteKeyPress"
@selectionChange="onSelectionChange"
@nodesSelection="onNodesSelection"
> >
<!-- Global SVG definitions for arrows --> <!-- Global SVG definitions for arrows -->
<svg style="position: absolute; top: 0; left: 0; width: 0; height: 0"> <svg style="position: absolute; top: 0; left: 0; width: 0; height: 0">
@ -1476,18 +1509,49 @@ function fromObject(flowObject) {
<template #edge-label="{ label }"> <template #edge-label="{ label }">
<div class="edge-label">{{ label }}</div> <div class="edge-label">{{ label }}</div>
</template> </template>
<Panel position="top-right" class="node-controls">
<div class="p-2 bg-white rounded shadow-sm text-sm"> <!-- Collapsible Help Guide -->
<div class="mb-1 font-medium">Controls:</div> <Panel position="top-right" class="help-guide">
<div> Delete: Remove selected</div> <div class="help-guide-content">
<div> Shift: Select nodes</div> <button
<div> Drag between nodes to connect</div> @click="toggleHelpGuide"
<div> Double-click to remove</div> class="help-toggle-btn"
<div class="mt-2 mb-1 font-medium">Edge Features:</div> :class="{ 'expanded': showHelpGuide }"
<div> Arrows show flow direction</div> >
<div> Select edge for controls</div> <Icon name="material-symbols:help-outline" class="w-4 h-4" />
<div> Blue dot = drag to reposition</div> <span v-if="showHelpGuide">Hide Help</span>
<div> Reset button restores path</div> <span v-else>Show Help</span>
</button>
<div v-if="showHelpGuide" class="help-content">
<div class="help-section">
<h4 class="help-title">Selection</h4>
<ul class="help-list">
<li> Click: Select single node</li>
<li> Drag: Select multiple nodes</li>
<li> Ctrl+Click: Add to selection</li>
<li> Drag selected nodes together</li>
</ul>
</div>
<div class="help-section">
<h4 class="help-title">Actions</h4>
<ul class="help-list">
<li> Delete: Remove selected</li>
<li> Double-click: Remove node/edge</li>
<li> Drag between nodes: Connect</li>
</ul>
</div>
<div class="help-section">
<h4 class="help-title">Edges</h4>
<ul class="help-list">
<li> Select edge for controls</li>
<li> Blue dot: Reposition</li>
<li> Reset: Restore path</li>
</ul>
</div>
</div>
</div> </div>
</Panel> </Panel>
</VueFlow> </VueFlow>
@ -1691,12 +1755,7 @@ function fromObject(flowObject) {
} }
.node-controls { .node-controls {
margin: 10px; display: none;
color: #666;
font-size: 12px;
background: white;
border-radius: 4px;
pointer-events: all;
} }
:deep(.vue-flow__handle) { :deep(.vue-flow__handle) {
@ -1758,4 +1817,126 @@ function fromObject(flowObject) {
0 0 30px rgba(96, 165, 250, 0.6); 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;
}
</style> </style>