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
|
// Setup window resize handler
|
||||||
window.addEventListener("resize", resizeFlow);
|
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
|
// This prevents repositioning existing nodes when the canvas mounts
|
||||||
const hasExistingNodes = props.initialNodes.length > 0;
|
const hasExistingNodes = props.initialNodes.length > 0;
|
||||||
const hasPositions = props.initialNodes.some(
|
const hasPositions = props.initialNodes.some(
|
||||||
(node) => node.position && (node.position.x !== 0 || node.position.y !== 0)
|
(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) {
|
if (!hasExistingNodes || !hasPositions) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fitView();
|
fitView();
|
||||||
@ -498,16 +499,18 @@ watch(
|
|||||||
addNodes([...nodesToAdd]); // Create a copy to avoid reactivity issues
|
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) => {
|
newNodes.forEach((newNode) => {
|
||||||
const existingNode = nodes.value.find((n) => n.id === newNode.id);
|
const existingNode = nodes.value.find((n) => n.id === newNode.id);
|
||||||
if (existingNode) {
|
if (existingNode) {
|
||||||
// Always update the existing node to ensure latest data is reflected
|
// FIXED: NEVER update positions for existing nodes during sync
|
||||||
// This is critical for property changes to be visible in the canvas
|
// 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, {
|
Object.assign(existingNode, {
|
||||||
label: newNode.label,
|
label: newNode.label,
|
||||||
data: { ...newNode.data },
|
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
|
// 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
|
// FIXED: Don't auto-fit view when adding individual nodes to prevent repositioning
|
||||||
if (nodesToAdd.length > 0) {
|
// Only fit view if this is a bulk operation (like loading a process) with many nodes
|
||||||
|
if (nodesToAdd.length > 3) {
|
||||||
await nextTick();
|
await nextTick();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fitView();
|
fitView();
|
||||||
@ -869,16 +873,16 @@ const onDrop = (event) => {
|
|||||||
newNode.connectable = false;
|
newNode.connectable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to Vue Flow for immediate visual feedback
|
// FIXED: Only emit to parent, don't add directly to canvas
|
||||||
addNodes([newNode]);
|
// The parent will add to store, which will trigger canvas update through watchers
|
||||||
|
// This prevents double node creation (canvas + store)
|
||||||
// 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
|
|
||||||
emit(
|
emit(
|
||||||
"nodesChange",
|
"nodesChange",
|
||||||
[{ type: "add", id: newNode.id, item: newNode }],
|
[{ type: "add", id: newNode.id, item: newNode }],
|
||||||
nodes.value
|
nodes.value
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('📦 Canvas: Drop handled, emitted to parent:', newNode.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error handling drop:", error);
|
console.error("Error handling drop:", error);
|
||||||
}
|
}
|
||||||
@ -1414,15 +1418,18 @@ function syncCanvas(newNodes, newEdges) {
|
|||||||
addNodes([...nodesToAdd]);
|
addNodes([...nodesToAdd]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update existing nodes - force update all properties
|
// Update existing nodes - STRICTLY PRESERVE CANVAS POSITIONS
|
||||||
newNodes.forEach((newNode) => {
|
newNodes.forEach((newNode) => {
|
||||||
const existingNode = nodes.value.find((n) => n.id === newNode.id);
|
const existingNode = nodes.value.find((n) => n.id === newNode.id);
|
||||||
if (existingNode) {
|
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, {
|
Object.assign(existingNode, {
|
||||||
label: newNode.label,
|
label: newNode.label,
|
||||||
data: { ...newNode.data },
|
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
|
// Always force re-render for property changes
|
||||||
updateNodeInternals([newNode.id]);
|
updateNodeInternals([newNode.id]);
|
||||||
|
@ -1360,12 +1360,8 @@ const handleConditionUpdate = async (conditions) => {
|
|||||||
const onNodesChange = (changes, currentNodes) => {
|
const onNodesChange = (changes, currentNodes) => {
|
||||||
if (!changes || !currentNodes) return;
|
if (!changes || !currentNodes) return;
|
||||||
|
|
||||||
// Skip processing during component addition to avoid conflicts
|
// Handle position changes FIRST (always allow position updates)
|
||||||
if (isAddingComponent.value) {
|
// This ensures user drag operations are saved to store
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle position changes (only when dragging is complete)
|
|
||||||
const positionChanges = {};
|
const positionChanges = {};
|
||||||
const positionChangesList = changes
|
const positionChangesList = changes
|
||||||
.filter(change => change.type === 'position' && change.position && !change.dragging);
|
.filter(change => change.type === 'position' && change.position && !change.dragging);
|
||||||
@ -1375,9 +1371,17 @@ const onNodesChange = (changes, currentNodes) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (Object.keys(positionChanges).length > 0) {
|
if (Object.keys(positionChanges).length > 0) {
|
||||||
|
console.log('📍 User moved nodes - updating store:', Object.keys(positionChanges));
|
||||||
processStore.updateNodePositions(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)
|
// Handle node removals (from delete key or other removal actions)
|
||||||
const removedNodes = changes
|
const removedNodes = changes
|
||||||
.filter(change => change.type === 'remove')
|
.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)
|
// FIXED: Handle node additions from canvas drops (drag and drop from component panel)
|
||||||
const addedNodes = changes
|
const addedNodes = changes.filter(change => change.type === 'add');
|
||||||
.filter(change => change.type === 'add')
|
|
||||||
.map(change => change.id);
|
|
||||||
|
|
||||||
if (addedNodes.length > 0) {
|
if (addedNodes.length > 0) {
|
||||||
// These nodes are already in the canvas, so we don't need to add them to the store
|
addedNodes.forEach(async (change) => {
|
||||||
// unless they're not already there
|
// 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
|
// REMOVED: Don't overwrite selectedNodeData from canvas changes to preserve local edits
|
||||||
@ -1804,7 +1831,7 @@ const onAddComponent = async (component) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
isAddingComponent.value = true;
|
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
|
// Create a new node from the component definition
|
||||||
// The component structure from ProcessBuilderComponents is:
|
// The component structure from ProcessBuilderComponents is:
|
||||||
@ -1870,50 +1897,32 @@ const onAddComponent = async (component) => {
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the node to the process store
|
// UPDATED: Add to store first, canvas sync will happen automatically through watchers
|
||||||
console.log('📝 Adding node to store:', newNode.id);
|
console.log('📝 Component Panel: Adding node to store:', newNode.id);
|
||||||
const addedNode = await processStore.addNode(newNode);
|
const addedNode = await processStore.addNode(newNode);
|
||||||
|
|
||||||
if (!addedNode) {
|
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.');
|
toast.error('Failed to add component to store. Please try again.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for store update and next render cycle
|
// Wait for store update and canvas sync through watchers
|
||||||
await nextTick();
|
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) {
|
} 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.');
|
toast.error('Failed to add component. Please try again.');
|
||||||
} finally {
|
} finally {
|
||||||
// Reset the flag after a longer delay to ensure canvas is stable
|
// Reset the flag after a longer delay to ensure canvas is stable
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log('🏁 Component addition completed, resetting flag');
|
console.log('🏁 Component Panel: Addition completed, resetting flag');
|
||||||
isAddingComponent.value = false;
|
isAddingComponent.value = false;
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user