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:
parent
5261bf601e
commit
80038e00a3
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user