Refactor Process Flow Canvas and Node Handling for Improved Stability
- Updated ProcessFlowCanvas.vue to ensure that existing node positions are preserved during updates, preventing unintended repositioning. - Enhanced node addition logic in index.vue to handle drops from the canvas correctly, ensuring that nodes are added to the store without duplicating entries. - Improved user experience by allowing position updates during drag operations while preventing conflicts during component additions. - Added console logs for better debugging and tracking of node operations.
This commit is contained in:
parent
312e555361
commit
ee91cd6c56
@ -308,13 +308,14 @@ onMounted(() => {
|
||||
// Setup window resize handler
|
||||
window.addEventListener("resize", resizeFlow);
|
||||
|
||||
// Only fit view initially if there are no existing nodes with positions
|
||||
// FIXED: Only fit view initially if there are no existing nodes OR if nodes have no positions
|
||||
// This prevents repositioning existing nodes when the canvas mounts
|
||||
const hasExistingNodes = props.initialNodes.length > 0;
|
||||
const hasPositions = props.initialNodes.some(
|
||||
(node) => node.position && (node.position.x !== 0 || node.position.y !== 0)
|
||||
);
|
||||
|
||||
// Only auto-fit if we have no nodes, or nodes have default/zero positions
|
||||
if (!hasExistingNodes || !hasPositions) {
|
||||
setTimeout(() => {
|
||||
fitView();
|
||||
@ -498,16 +499,18 @@ watch(
|
||||
addNodes([...nodesToAdd]); // Create a copy to avoid reactivity issues
|
||||
}
|
||||
|
||||
// Update existing nodes that have changed
|
||||
// Update existing nodes that have changed - STRICTLY PRESERVE CANVAS POSITIONS
|
||||
newNodes.forEach((newNode) => {
|
||||
const existingNode = nodes.value.find((n) => n.id === newNode.id);
|
||||
if (existingNode) {
|
||||
// Always update the existing node to ensure latest data is reflected
|
||||
// This is critical for property changes to be visible in the canvas
|
||||
// FIXED: NEVER update positions for existing nodes during sync
|
||||
// The canvas is the source of truth for positions once a node exists
|
||||
// Only update data and label, preserve the current canvas position
|
||||
Object.assign(existingNode, {
|
||||
label: newNode.label,
|
||||
data: { ...newNode.data },
|
||||
position: { ...newNode.position },
|
||||
// ALWAYS keep the current canvas position - never overwrite from store
|
||||
position: existingNode.position,
|
||||
});
|
||||
|
||||
// Always call updateNodeInternals to force re-render
|
||||
@ -516,8 +519,9 @@ watch(
|
||||
}
|
||||
});
|
||||
|
||||
// Fit view only if we added new nodes and this is significant change
|
||||
if (nodesToAdd.length > 0) {
|
||||
// FIXED: Don't auto-fit view when adding individual nodes to prevent repositioning
|
||||
// Only fit view if this is a bulk operation (like loading a process) with many nodes
|
||||
if (nodesToAdd.length > 3) {
|
||||
await nextTick();
|
||||
setTimeout(() => {
|
||||
fitView();
|
||||
@ -869,16 +873,16 @@ const onDrop = (event) => {
|
||||
newNode.connectable = false;
|
||||
}
|
||||
|
||||
// Add to Vue Flow for immediate visual feedback
|
||||
addNodes([newNode]);
|
||||
|
||||
// IMPORTANT: Also emit the node to be added to the process store
|
||||
// This ensures the node persists in the application state and can be saved
|
||||
// FIXED: Only emit to parent, don't add directly to canvas
|
||||
// The parent will add to store, which will trigger canvas update through watchers
|
||||
// This prevents double node creation (canvas + store)
|
||||
emit(
|
||||
"nodesChange",
|
||||
[{ type: "add", id: newNode.id, item: newNode }],
|
||||
nodes.value
|
||||
);
|
||||
|
||||
console.log('📦 Canvas: Drop handled, emitted to parent:', newNode.id);
|
||||
} catch (error) {
|
||||
console.error("Error handling drop:", error);
|
||||
}
|
||||
@ -1414,15 +1418,18 @@ function syncCanvas(newNodes, newEdges) {
|
||||
addNodes([...nodesToAdd]);
|
||||
}
|
||||
|
||||
// Update existing nodes - force update all properties
|
||||
// Update existing nodes - STRICTLY PRESERVE CANVAS POSITIONS
|
||||
newNodes.forEach((newNode) => {
|
||||
const existingNode = nodes.value.find((n) => n.id === newNode.id);
|
||||
if (existingNode) {
|
||||
// Always update to ensure property changes are reflected
|
||||
// FIXED: NEVER update positions for existing nodes during manual sync
|
||||
// The canvas is the source of truth for positions once a node exists
|
||||
// Only update data and label, preserve the current canvas position
|
||||
Object.assign(existingNode, {
|
||||
label: newNode.label,
|
||||
data: { ...newNode.data },
|
||||
position: { ...newNode.position },
|
||||
// ALWAYS keep the current canvas position - never overwrite from store
|
||||
position: existingNode.position,
|
||||
});
|
||||
// Always force re-render for property changes
|
||||
updateNodeInternals([newNode.id]);
|
||||
|
@ -1360,12 +1360,8 @@ const handleConditionUpdate = async (conditions) => {
|
||||
const onNodesChange = (changes, currentNodes) => {
|
||||
if (!changes || !currentNodes) return;
|
||||
|
||||
// Skip processing during component addition to avoid conflicts
|
||||
if (isAddingComponent.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle position changes (only when dragging is complete)
|
||||
// Handle position changes FIRST (always allow position updates)
|
||||
// This ensures user drag operations are saved to store
|
||||
const positionChanges = {};
|
||||
const positionChangesList = changes
|
||||
.filter(change => change.type === 'position' && change.position && !change.dragging);
|
||||
@ -1375,9 +1371,17 @@ const onNodesChange = (changes, currentNodes) => {
|
||||
});
|
||||
|
||||
if (Object.keys(positionChanges).length > 0) {
|
||||
console.log('📍 User moved nodes - updating store:', Object.keys(positionChanges));
|
||||
processStore.updateNodePositions(positionChanges);
|
||||
}
|
||||
|
||||
// Skip other processing during component addition to avoid conflicts
|
||||
// But ALWAYS allow position updates above
|
||||
if (isAddingComponent.value) {
|
||||
console.log('🚫 Skipping other node changes during component addition');
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle node removals (from delete key or other removal actions)
|
||||
const removedNodes = changes
|
||||
.filter(change => change.type === 'remove')
|
||||
@ -1394,14 +1398,37 @@ const onNodesChange = (changes, currentNodes) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle node additions (this should be rare since we add nodes through the store first)
|
||||
const addedNodes = changes
|
||||
.filter(change => change.type === 'add')
|
||||
.map(change => change.id);
|
||||
// FIXED: Handle node additions from canvas drops (drag and drop from component panel)
|
||||
const addedNodes = changes.filter(change => change.type === 'add');
|
||||
|
||||
if (addedNodes.length > 0) {
|
||||
// These nodes are already in the canvas, so we don't need to add them to the store
|
||||
// unless they're not already there
|
||||
addedNodes.forEach(async (change) => {
|
||||
// Check if this node came from a canvas drop event (has item property)
|
||||
if (change.item) {
|
||||
console.log('📦 Parent: Received drop event for node:', change.item.id);
|
||||
|
||||
// Set flag to prevent interference
|
||||
isAddingComponent.value = true;
|
||||
|
||||
try {
|
||||
// Add the node to store only (canvas already has it from the drop)
|
||||
const addedNode = await processStore.addNode(change.item);
|
||||
|
||||
if (addedNode) {
|
||||
// Select the newly added node
|
||||
onNodeSelected(addedNode);
|
||||
console.log('✅ Parent: Successfully added dropped node to store:', addedNode.id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Parent: Error adding dropped node to store:', error);
|
||||
} finally {
|
||||
// Reset flag after a delay to ensure stability
|
||||
setTimeout(() => {
|
||||
isAddingComponent.value = false;
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// REMOVED: Don't overwrite selectedNodeData from canvas changes to preserve local edits
|
||||
@ -1804,7 +1831,7 @@ const onAddComponent = async (component) => {
|
||||
|
||||
try {
|
||||
isAddingComponent.value = true;
|
||||
console.log('🎯 Adding component:', component.type);
|
||||
console.log('🎯 Component Panel: Adding component:', component.type);
|
||||
|
||||
// Create a new node from the component definition
|
||||
// The component structure from ProcessBuilderComponents is:
|
||||
@ -1870,50 +1897,32 @@ const onAddComponent = async (component) => {
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Add the node to the process store
|
||||
console.log('📝 Adding node to store:', newNode.id);
|
||||
// UPDATED: Add to store first, canvas sync will happen automatically through watchers
|
||||
console.log('📝 Component Panel: Adding node to store:', newNode.id);
|
||||
const addedNode = await processStore.addNode(newNode);
|
||||
|
||||
if (!addedNode) {
|
||||
console.error('❌ Failed to add node to store');
|
||||
console.error('❌ Component Panel: Failed to add node to store');
|
||||
toast.error('Failed to add component to store. Please try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for store update and next render cycle
|
||||
// Wait for store update and canvas sync through watchers
|
||||
await nextTick();
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Select the newly added node after it's stable
|
||||
onNodeSelected(addedNode);
|
||||
|
||||
console.log('✅ Component Panel: Successfully added node:', addedNode.id, 'at position:', addedNode.position);
|
||||
|
||||
// Add the node directly to the canvas using Vue Flow's addNode method
|
||||
if (processFlowCanvas.value) {
|
||||
try {
|
||||
console.log('🎨 Adding node to canvas:', addedNode.id);
|
||||
|
||||
// Use Vue Flow's addNode method directly to avoid any sync issues
|
||||
if (processFlowCanvas.value.addNode) {
|
||||
processFlowCanvas.value.addNode(addedNode);
|
||||
}
|
||||
|
||||
// Wait for the node to be added to the canvas
|
||||
await nextTick();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Select the newly added node after it's stable
|
||||
onNodeSelected(addedNode);
|
||||
|
||||
console.log('✅ Successfully added new node:', addedNode.id, 'at position:', addedNode.position);
|
||||
} catch (error) {
|
||||
console.error('❌ Error adding node to canvas:', error);
|
||||
toast.error('Failed to add component to canvas. Please try again.');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error adding component:', error);
|
||||
console.error('❌ Component Panel: Error adding component:', error);
|
||||
toast.error('Failed to add component. Please try again.');
|
||||
} finally {
|
||||
// Reset the flag after a longer delay to ensure canvas is stable
|
||||
setTimeout(() => {
|
||||
console.log('🏁 Component addition completed, resetting flag');
|
||||
console.log('🏁 Component Panel: Addition completed, resetting flag');
|
||||
isAddingComponent.value = false;
|
||||
}, 300);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user