+
+
-
+
-
{{ template.name }}
+
{{ template.name }}
+
+
+
+
-
{{ template.name }}
-
{{ template.description }}
+
{{ template.name }}
+
{{ template.description }}
-
-
+
+
{{ template.nodeCount }} nodes
-
+
{{ template.edgeCount }} connections
- {{ template.complexity }}
+ {{ template.complexity }}
-
- Use Template
-
+
+
+
+ Preview
+
+
+
+ Use Template
+
+
@@ -62,9 +89,148 @@
+
+
+
+
+
+
+
+
+
{{ selectedTemplate.name }}
+
{{ selectedTemplate.description }}
+
+
+
+ {{ selectedTemplate.complexity }}
+
+
+ {{ selectedTemplate.category }}
+
+
+
+
+
+
+
+ {{ selectedTemplate.nodeCount }} nodes
+
+
+
+ {{ selectedTemplate.edgeCount }} connections
+
+
+
+ {{ selectedTemplate.variables?.length || 0 }} variables
+
+
+
+
+
+
+
Process Flow
+
+
+
+
+
+
+
+
+
+
+
+
Process Variables
+
+
+
+ {{ variable.name }}
+
+ {{ variable.type }}
+
+
+
{{ variable.description }}
+
+ Default:
+ {{ variable.defaultValue }}
+
+
+
+
+
+
+
+
+ Close
+
+
+
+ Use This Template
+
+
+
+
\ No newline at end of file
diff --git a/components/process-flow/ProcessBuilderComponents.vue b/components/process-flow/ProcessBuilderComponents.vue
index 2b6cb0f..18291cb 100644
--- a/components/process-flow/ProcessBuilderComponents.vue
+++ b/components/process-flow/ProcessBuilderComponents.vue
@@ -74,7 +74,11 @@ const availableComponents = [
defaultProps: {
label: 'Start',
data: {
- description: 'Process start point'
+ description: 'Process start point',
+ shape: 'circle',
+ backgroundColor: '#dcfce7',
+ borderColor: '#10b981',
+ textColor: '#065f46'
}
}
},
@@ -87,7 +91,11 @@ const availableComponents = [
defaultProps: {
label: 'End',
data: {
- description: 'Process end point'
+ description: 'Process end point',
+ shape: 'circle',
+ backgroundColor: '#fee2e2',
+ borderColor: '#dc2626',
+ textColor: '#991b1b'
}
}
},
@@ -103,7 +111,11 @@ const availableComponents = [
data: {
description: 'Form submission task',
formId: null,
- formName: null
+ formName: null,
+ shape: 'rectangle',
+ backgroundColor: '#faf5ff',
+ borderColor: '#9333ea',
+ textColor: '#6b21a8'
}
}
},
@@ -123,7 +135,11 @@ const availableComponents = [
headers: '{ "Content-Type": "application/json" }',
outputVariable: 'apiResponse',
continueOnError: false,
- errorVariable: 'apiError'
+ errorVariable: 'apiError',
+ shape: 'rectangle',
+ backgroundColor: '#eff6ff',
+ borderColor: '#3b82f6',
+ textColor: '#1e40af'
}
}
},
@@ -138,7 +154,11 @@ const availableComponents = [
data: {
description: 'Decision point for branching the workflow',
conditions: [],
- defaultPath: 'Default'
+ defaultPath: 'Default',
+ shape: 'diamond',
+ backgroundColor: '#fff7ed',
+ borderColor: '#f97316',
+ textColor: '#c2410c'
}
}
},
@@ -170,7 +190,11 @@ const availableComponents = [
enabled: false,
value: 24,
unit: 'hours'
- }
+ },
+ shape: 'rectangle',
+ backgroundColor: '#f0f9ff',
+ borderColor: '#0ea5e9',
+ textColor: '#0284c7'
}
}
},
@@ -185,7 +209,11 @@ const availableComponents = [
data: {
description: 'Apply business rules',
ruleGroups: [],
- priority: 'medium'
+ priority: 'medium',
+ shape: 'rectangle',
+ backgroundColor: '#fdf4ff',
+ borderColor: '#a855f7',
+ textColor: '#7c3aed'
}
}
},
@@ -202,7 +230,11 @@ const availableComponents = [
scriptCode: '',
scriptLanguage: 'javascript',
inputVariables: [],
- outputVariables: []
+ outputVariables: [],
+ shape: 'rectangle',
+ backgroundColor: '#f9fafb',
+ borderColor: '#6b7280',
+ textColor: '#374151'
}
}
},
@@ -222,7 +254,11 @@ const availableComponents = [
inputVariables: [],
outputVariables: [],
allowVariableAccess: true,
- autoRefresh: false
+ autoRefresh: false,
+ shape: 'rectangle',
+ backgroundColor: '#e0f2fe',
+ borderColor: '#0ea5e9',
+ textColor: '#0c4a6e'
}
}
},
@@ -237,7 +273,11 @@ const availableComponents = [
data: {
description: 'Executes another process',
subprocessId: null,
- subprocessName: ''
+ subprocessName: '',
+ shape: 'rectangle',
+ backgroundColor: '#f0fdfa',
+ borderColor: '#14b8a6',
+ textColor: '#134e4a'
}
}
},
@@ -362,7 +402,6 @@ const onDragStart = (event, component) => {
// Add a component directly via click
const addComponent = (component) => {
- return;
// Use same format as drag operation for consistency
const componentData = {
type: component.type,
diff --git a/components/process-flow/ProcessFlowCanvas.vue b/components/process-flow/ProcessFlowCanvas.vue
index 16fa0d4..51edea1 100644
--- a/components/process-flow/ProcessFlowCanvas.vue
+++ b/components/process-flow/ProcessFlowCanvas.vue
@@ -39,24 +39,24 @@ import "@vue-flow/minimap/dist/style.css";
// Create nodeTypes object with markRaw to prevent reactivity issues
const customNodeTypes = {
- 'start': markRaw(StartNode),
- 'end': markRaw(EndNode),
- 'form': markRaw(FormNode),
- 'api': markRaw(ApiNode),
- 'gateway': markRaw(GatewayNode),
- 'script': markRaw(ScriptNode),
- 'business-rule': markRaw(BusinessRuleNode),
- 'notification': markRaw(NotificationNode),
- 'html': markRaw(HtmlNode),
- 'subprocess': markRaw(SubprocessNode),
+ start: markRaw(StartNode),
+ end: markRaw(EndNode),
+ form: markRaw(FormNode),
+ api: markRaw(ApiNode),
+ gateway: markRaw(GatewayNode),
+ script: markRaw(ScriptNode),
+ "business-rule": markRaw(BusinessRuleNode),
+ notification: markRaw(NotificationNode),
+ html: markRaw(HtmlNode),
+ subprocess: markRaw(SubprocessNode),
// Shape nodes
- 'hexagon-shape': markRaw(HexagonShape),
- 'trapezoid-shape': markRaw(TrapezoidShape),
- 'rectangle-shape': markRaw(RectangleShape),
- 'swimlane-horizontal': markRaw(SwimlaneHorizontal),
- 'swimlane-vertical': markRaw(SwimlaneVertical),
- 'text-annotation': markRaw(TextAnnotation),
- 'process-group': markRaw(ProcessGroup)
+ "hexagon-shape": markRaw(HexagonShape),
+ "trapezoid-shape": markRaw(TrapezoidShape),
+ "rectangle-shape": markRaw(RectangleShape),
+ "swimlane-horizontal": markRaw(SwimlaneHorizontal),
+ "swimlane-vertical": markRaw(SwimlaneVertical),
+ "text-annotation": markRaw(TextAnnotation),
+ "process-group": markRaw(ProcessGroup),
};
// Add Material Icons import
@@ -276,10 +276,18 @@ onMounted(() => {
// Setup window resize handler
window.addEventListener("resize", resizeFlow);
- // Initial fit view
- setTimeout(() => {
- fitView();
- }, 100);
+ // Only fit view initially if there are no existing nodes with 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)
+ );
+
+ if (!hasExistingNodes || !hasPositions) {
+ setTimeout(() => {
+ fitView();
+ }, 100);
+ }
});
// Center on a specific node
@@ -869,7 +877,7 @@ defineExpose({
nodes.value = newNodes;
}
} catch (error) {
- console.error('Error in setNodes:', error);
+ console.error("Error in setNodes:", error);
}
},
setEdges: (newEdges) => {
@@ -878,13 +886,13 @@ defineExpose({
edges.value = newEdges;
}
} catch (error) {
- console.error('Error in setEdges:', error);
+ console.error("Error in setEdges:", error);
}
},
// Provide access to the flow instance
get flowInstance() {
return flowInstance;
- }
+ },
});
// Update an existing node
@@ -978,15 +986,22 @@ function syncCanvas(newNodes, newEdges) {
);
// Remove nodes that are no longer in the new list
+ // Be more conservative - only remove if we're sure it's not a temporary state
const nodesToRemove = nodes.value.filter(
(node) => !newNodeIds.has(node.id)
);
- if (nodesToRemove.length > 0) {
+
+ // Only remove nodes if the new list is not empty (prevents accidental clearing)
+ if (nodesToRemove.length > 0 && newNodes.length > 0) {
console.log(
"🗑️ Removing nodes:",
nodesToRemove.map((n) => n.id)
);
removeNodes(nodesToRemove);
+ } else if (nodesToRemove.length > 0 && newNodes.length === 0) {
+ console.log(
+ "⚠️ Not removing nodes - new list is empty, might be temporary state"
+ );
}
// Add new nodes that aren't already present
@@ -1254,22 +1269,51 @@ function fromObject(flowObject) {
// Wait for any pending operations to complete
setTimeout(async () => {
try {
- // 1. First, clear existing state
- if (nodes.value.length > 0) {
- removeNodes([...nodes.value]);
- }
- if (edges.value.length > 0) {
- removeEdges([...edges.value]);
+ // Check if we're restoring the same data that's already there
+ const currentNodes = nodes.value.map((n) => ({
+ id: n.id,
+ type: n.type,
+ position: n.position,
+ }));
+ const newNodes =
+ flowObject.nodes?.map((n) => ({
+ id: n.id,
+ type: n.type,
+ position: n.position,
+ })) || [];
+
+ const nodesAreSame =
+ currentNodes.length === newNodes.length &&
+ currentNodes.every(
+ (node, index) =>
+ node.id === newNodes[index]?.id &&
+ node.type === newNodes[index]?.type
+ );
+
+ // Only clear if we're actually restoring different data
+ if (!nodesAreSame) {
+ // 1. First, clear existing state
+ if (nodes.value.length > 0) {
+ console.log("🔄 Clearing existing nodes for restoration");
+ removeNodes([...nodes.value]);
+ }
+ if (edges.value.length > 0) {
+ console.log("🔄 Clearing existing edges for restoration");
+ removeEdges([...edges.value]);
+ }
+ } else {
+ console.log("✅ Nodes are the same, skipping clear operation");
}
// Wait for clearing to complete
await nextTick();
- // 2. Restore nodes first
+ // 2. Restore nodes first (only if we cleared or if no nodes exist)
if (
flowObject.nodes &&
Array.isArray(flowObject.nodes) &&
- flowObject.nodes.length > 0
+ flowObject.nodes.length > 0 &&
+ (!nodesAreSame || nodes.value.length === 0)
) {
const nodesToRestore = flowObject.nodes.map((node) => ({
id: node.id,
@@ -1280,19 +1324,23 @@ function fromObject(flowObject) {
// Only include essential properties needed for Vue Flow
}));
+ console.log("🔄 Restoring", nodesToRestore.length, "nodes");
addNodes(nodesToRestore);
await nextTick();
// Wait a bit more for nodes to be fully initialized
await new Promise((resolve) => setTimeout(resolve, 100));
+ } else if (nodesAreSame) {
+ console.log("✅ Nodes already exist, skipping node restoration");
}
- // 3. Restore edges after nodes are ready
+ // 3. Restore edges after nodes are ready (only if we cleared or if no edges exist)
if (
flowObject.edges &&
Array.isArray(flowObject.edges) &&
flowObject.edges.length > 0 &&
- nodes.value.length > 0
+ nodes.value.length > 0 &&
+ (!nodesAreSame || edges.value.length === 0)
) {
// Verify all edges have valid source and target nodes
const validEdges = flowObject.edges.filter((edge) => {
@@ -1328,9 +1376,12 @@ function fromObject(flowObject) {
// Only include essential properties
}));
+ console.log("🔄 Restoring", cleanEdges.length, "edges");
addEdges(cleanEdges);
await nextTick();
}
+ } else if (nodesAreSame) {
+ console.log("✅ Edges already exist, skipping edge restoration");
}
// 4. Finally, restore viewport position and zoom
diff --git a/pages/process-builder/index.vue b/pages/process-builder/index.vue
index b4f4d7b..8f0a469 100644
--- a/pages/process-builder/index.vue
+++ b/pages/process-builder/index.vue
@@ -1331,25 +1331,37 @@ const saveProcess = async () => {
// Add a component handler to add components from the component panel
const onAddComponent = async (component) => {
- if (isAddingComponent.value) return; // Prevent concurrent additions
+ if (isAddingComponent.value || isProcessLoading.value) return; // Prevent concurrent additions or interference with process loading
try {
isAddingComponent.value = true;
+ console.log('🎯 Adding component:', component.type);
// Create a new node from the component definition
+ // The component structure from ProcessBuilderComponents is:
+ // { type, label, data: { ...defaultProps.data } }
+
+ // Calculate a better position for the new node to avoid overlap
+ let nodePosition = { x: 100, y: 100 };
+ if (processStore.currentProcess && processStore.currentProcess.nodes.length > 0) {
+ // Find the rightmost and bottommost positions
+ const maxX = Math.max(...processStore.currentProcess.nodes.map(n => n.position?.x || 0));
+ const maxY = Math.max(...processStore.currentProcess.nodes.map(n => n.position?.y || 0));
+
+ // Place new node to the right with some spacing
+ nodePosition = {
+ x: maxX + 200, // 200px spacing from rightmost node
+ y: Math.max(100, maxY) // At least 100px from top, or at the level of the bottommost node
+ };
+ }
+
const newNode = {
id: `${component.type}_${Date.now()}`,
type: component.type,
- position: { x: 100, y: 100 }, // Default position
+ position: nodePosition,
label: component.label,
data: {
- ...component.data,
- // Ensure shape is set for new nodes
- shape: component.data.shape || (component.type === 'gateway' ? 'diamond' : 'rectangle'),
- // Ensure default colors are set for new nodes
- backgroundColor: component.data.backgroundColor,
- borderColor: component.data.borderColor,
- textColor: component.data.textColor
+ ...component.data
}
};
@@ -1390,78 +1402,51 @@ const onAddComponent = async (component) => {
}
// Add the node to the process store
- await processStore.addNode(newNode);
+ console.log('📝 Adding node to store:', newNode.id);
+ const addedNode = await processStore.addNode(newNode);
+
+ if (!addedNode) {
+ console.error('❌ 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
await nextTick();
await new Promise(resolve => setTimeout(resolve, 50));
- // CRITICAL FIX: Instead of calling syncCanvas (which can cause edge removal/re-addition),
- // we'll add the node directly to the canvas and preserve existing edges
+ // Add the node directly to the canvas using Vue Flow's addNode method
if (processFlowCanvas.value) {
try {
- // Get the fresh node from store (with any store-side modifications)
- const freshNode = processStore.currentProcess?.nodes.find(n => n.id === newNode.id);
+ console.log('🎨 Adding node to canvas:', addedNode.id);
- if (freshNode && processFlowCanvas.value.addNode) {
- // Add only the new node to the canvas directly
- processFlowCanvas.value.addNode(freshNode);
-
- // Wait for the node to be added to the canvas
- await nextTick();
- await new Promise(resolve => setTimeout(resolve, 50));
-
- // Select the newly added node after it's stable
- onNodeSelected(freshNode);
-
- console.log('✅ Successfully added new node without affecting existing edges');
- } else {
- console.warn('⚠️ Fresh node not found in store, falling back to full sync');
- // Fallback to full sync if something went wrong
- const currentNodes = processStore.currentProcess?.nodes || [];
- const currentEdges = processStore.currentProcess?.edges || [];
-
- if (processFlowCanvas.value.syncCanvas) {
- processFlowCanvas.value.syncCanvas(currentNodes, currentEdges);
-
- await nextTick();
- await new Promise(resolve => setTimeout(resolve, 50));
-
- const addedNode = currentNodes.find(n => n.id === newNode.id);
- if (addedNode) {
- onNodeSelected(addedNode);
- }
- }
+ // 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);
-
- // Fallback to full sync if direct addition fails
- const currentNodes = processStore.currentProcess?.nodes || [];
- const currentEdges = processStore.currentProcess?.edges || [];
-
- if (processFlowCanvas.value.syncCanvas) {
- console.log('🔄 Falling back to full canvas sync due to error');
- processFlowCanvas.value.syncCanvas(currentNodes, currentEdges);
-
- await nextTick();
- await new Promise(resolve => setTimeout(resolve, 50));
-
- const addedNode = currentNodes.find(n => n.id === newNode.id);
- if (addedNode) {
- onNodeSelected(addedNode);
- }
- }
+ toast.error('Failed to add component to canvas. Please try again.');
}
}
} catch (error) {
- console.error('Error adding component:', error);
+ console.error('❌ 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');
isAddingComponent.value = false;
- }, 200);
+ }, 300);
}
};
@@ -1504,6 +1489,9 @@ const applyProcessTemplate = async (template) => {
// Process nodes first and wait for them to be fully added
for (const node of templateNodes) {
+ // Get default styling for the node type
+ const defaultStyling = getDefaultNodeStyling(node.type);
+
const newNode = {
...node,
id: node.id, // Keep original ID for edge references
@@ -1511,7 +1499,12 @@ const applyProcessTemplate = async (template) => {
position: node.position || { x: 100, y: 100 },
data: {
...node.data,
- label: node.data?.label || node.label || `${node.type} node`
+ label: node.data?.label || node.label || `${node.type} node`,
+ // Preserve styling properties from template or use defaults
+ backgroundColor: node.data?.backgroundColor || defaultStyling.backgroundColor,
+ borderColor: node.data?.borderColor || defaultStyling.borderColor,
+ textColor: node.data?.textColor || defaultStyling.textColor,
+ shape: node.data?.shape || defaultStyling.shape
}
};
@@ -1584,6 +1577,78 @@ const applyProcessTemplate = async (template) => {
}
};
+// Get default styling for node types
+const getDefaultNodeStyling = (nodeType) => {
+ const stylingMap = {
+ 'start': {
+ backgroundColor: '#dcfce7',
+ borderColor: '#10b981',
+ textColor: '#065f46',
+ shape: 'circle'
+ },
+ 'end': {
+ backgroundColor: '#fee2e2',
+ borderColor: '#dc2626',
+ textColor: '#991b1b',
+ shape: 'circle'
+ },
+ 'form': {
+ backgroundColor: '#faf5ff',
+ borderColor: '#9333ea',
+ textColor: '#6b21a8',
+ shape: 'rectangle'
+ },
+ 'api': {
+ backgroundColor: '#eff6ff',
+ borderColor: '#3b82f6',
+ textColor: '#1e40af',
+ shape: 'rectangle'
+ },
+ 'gateway': {
+ backgroundColor: '#fff7ed',
+ borderColor: '#f97316',
+ textColor: '#c2410c',
+ shape: 'diamond'
+ },
+ 'notification': {
+ backgroundColor: '#f0f9ff',
+ borderColor: '#0ea5e9',
+ textColor: '#0284c7',
+ shape: 'rectangle'
+ },
+ 'business-rule': {
+ backgroundColor: '#fdf4ff',
+ borderColor: '#a855f7',
+ textColor: '#7c3aed',
+ shape: 'rectangle'
+ },
+ 'script': {
+ backgroundColor: '#f9fafb',
+ borderColor: '#6b7280',
+ textColor: '#374151',
+ shape: 'rectangle'
+ },
+ 'html': {
+ backgroundColor: '#e0f2fe',
+ borderColor: '#0ea5e9',
+ textColor: '#0c4a6e',
+ shape: 'rectangle'
+ },
+ 'subprocess': {
+ backgroundColor: '#f0fdfa',
+ borderColor: '#14b8a6',
+ textColor: '#134e4a',
+ shape: 'rectangle'
+ }
+ };
+
+ return stylingMap[nodeType] || {
+ backgroundColor: '#ffffff',
+ borderColor: '#e2e8f0',
+ textColor: '#374151',
+ shape: 'rectangle'
+ };
+};
// Fix references to functions
const onFormSelected = (formData) => {
if (selectedNodeData.value && selectedNodeData.value.type === 'form') {
@@ -1818,62 +1883,85 @@ watch(() => canvasNodes.value, (newNodes) => {
}
}, { deep: true });
-// Watch for process changes to restore Vue Flow state
-watch(() => processStore.currentProcess, async (newProcess, oldProcess) => {
- if (!newProcess) return;
-
- // Only restore when a different process is loaded (not on updates)
- if (oldProcess && newProcess.id === oldProcess.id) return;
-
+// Track if we're in the middle of adding a component to prevent interference
+const isProcessLoading = ref(false);
+// Watch for process changes to restore Vue Flow state
+// Only trigger when the entire process object changes (different process loaded)
+watch(() => processStore.currentProcess?.id, async (newProcessId, oldProcessId) => {
+ if (!newProcessId) return;
+
+ // Only restore when a different process is loaded (not on updates to the same process)
+ if (oldProcessId && newProcessId === oldProcessId) return;
+
+ // Don't interfere if we're in the middle of adding a component or modifying nodes
+ if (isAddingComponent.value || processStore.isModifyingNodes) {
+ console.log('🚫 Skipping process restoration during component/node modification');
+ return;
+ }
+
+ const newProcess = processStore.currentProcess;
+ console.log('🔄 Loading process:', newProcessId, 'Nodes:', newProcess.nodes?.length || 0);
+ isProcessLoading.value = true;
// Wait for the canvas to be ready
await nextTick();
// Give the canvas component a moment to initialize
setTimeout(async () => {
- if (processFlowCanvas.value && processFlowCanvas.value.fromObject) {
- try {
- // Prepare the flow object for restoration
- const rawFlowObject = {
- nodes: newProcess.nodes || [],
- edges: newProcess.edges || [],
- viewport: newProcess.viewport || { x: 0, y: 0, zoom: 1 }
- };
-
- // Clean the data to remove any Vue Flow internal properties
- const flowObject = processStore.cleanFlowData(rawFlowObject);
-
-
-
- // Use Vue Flow's proper restoration method (now returns a Promise)
- await processFlowCanvas.value.fromObject(flowObject);
-
-
-
- // Fit view after restoration with a small delay to ensure everything is rendered
- setTimeout(() => {
- if (processFlowCanvas.value && processFlowCanvas.value.fitView) {
- processFlowCanvas.value.fitView();
+ try {
+ if (processFlowCanvas.value && processFlowCanvas.value.fromObject) {
+ try {
+ // Prepare the flow object for restoration
+ const rawFlowObject = {
+ nodes: newProcess.nodes || [],
+ edges: newProcess.edges || [],
+ viewport: newProcess.viewport || { x: 0, y: 0, zoom: 1 }
+ };
+
+ // Clean the data to remove any Vue Flow internal properties
+ const flowObject = processStore.cleanFlowData(rawFlowObject);
+
+ console.log('🔄 Restoring Vue Flow state with', flowObject.nodes.length, 'nodes');
+
+ // Use Vue Flow's proper restoration method (now returns a Promise)
+ await processFlowCanvas.value.fromObject(flowObject);
+
+ console.log('✅ Vue Flow state restored successfully');
+
+ // Only fit view if nodes don't have meaningful positions (all at 0,0)
+ // This prevents repositioning nodes that were carefully positioned
+ const hasPositionedNodes = flowObject.nodes.some(node =>
+ node.position && (node.position.x !== 0 || node.position.y !== 0)
+ );
+
+ if (!hasPositionedNodes && flowObject.nodes.length > 0) {
+ setTimeout(() => {
+ if (processFlowCanvas.value && processFlowCanvas.value.fitView) {
+ processFlowCanvas.value.fitView();
+ }
+ }, 300);
}
- }, 300);
+
+ } catch (error) {
+ console.error('❌ Error restoring Vue Flow state:', error);
+
+ // Fallback to manual sync if Vue Flow restoration fails
+ if (processFlowCanvas.value && processFlowCanvas.value.syncCanvas) {
+ console.log('🔄 Falling back to manual sync');
+ processFlowCanvas.value.syncCanvas(newProcess.nodes, newProcess.edges);
+ }
+ }
+ } else {
+ console.warn('⚠️ Vue Flow canvas not available for restoration, using sync fallback');
- } catch (error) {
- console.error('❌ Error restoring Vue Flow state:', error);
-
- // Fallback to manual sync if Vue Flow restoration fails
+ // Fallback sync method
if (processFlowCanvas.value && processFlowCanvas.value.syncCanvas) {
-
processFlowCanvas.value.syncCanvas(newProcess.nodes, newProcess.edges);
}
}
- } else {
- console.warn('⚠️ Vue Flow canvas not available for restoration, using sync fallback');
-
- // Fallback sync method
- if (processFlowCanvas.value && processFlowCanvas.value.syncCanvas) {
- processFlowCanvas.value.syncCanvas(newProcess.nodes, newProcess.edges);
- }
+ } finally {
+ isProcessLoading.value = false;
}
}, 200); // Allow time for canvas to initialize
}, { immediate: false });
diff --git a/stores/processBuilder.js b/stores/processBuilder.js
index 3675eae..8c24220 100644
--- a/stores/processBuilder.js
+++ b/stores/processBuilder.js
@@ -11,7 +11,8 @@ export const useProcessBuilderStore = defineStore('processBuilder', {
history: [],
historyIndex: -1,
unsavedChanges: false,
- lastChangeDescription: ''
+ lastChangeDescription: '',
+ isModifyingNodes: false // Flag to prevent interference during node operations
}),
getters: {
@@ -653,37 +654,48 @@ export const useProcessBuilderStore = defineStore('processBuilder', {
return existingNode;
}
- // Create a new node with proper data structure
- const newNode = {
- id: node.id || uuidv4(),
- type: node.type,
- label: node.label || node.data?.label || 'New Node',
- position: node.position || { x: 100, y: 100 },
- data: {
- ...node.data,
- label: node.data?.label || node.label || 'New Node',
- // Ensure shape is set for new nodes
- shape: node.data?.shape || (node.type === 'gateway' ? 'diamond' : 'rectangle'),
- // Ensure default colors are set for new nodes
- backgroundColor: node.data?.backgroundColor || '#ffffff',
- borderColor: node.data?.borderColor || '#000000'
- }
- };
+ // Set flag to prevent interference during node addition
+ this.isModifyingNodes = true;
- // Create a deep copy to avoid reference issues
- const nodeCopy = JSON.parse(JSON.stringify(newNode));
+ try {
+ // Create a new node with proper data structure
+ const newNode = {
+ id: node.id || uuidv4(),
+ type: node.type,
+ label: node.label || node.data?.label || 'New Node',
+ position: node.position || { x: 100, y: 100 },
+ data: {
+ ...node.data,
+ label: node.data?.label || node.label || 'New Node',
+ // Ensure shape is set for new nodes
+ shape: node.data?.shape || (node.type === 'gateway' ? 'diamond' : 'rectangle'),
+ // Ensure default colors are set for new nodes
+ backgroundColor: node.data?.backgroundColor || '#ffffff',
+ borderColor: node.data?.borderColor || '#000000'
+ }
+ };
- // Add to current process nodes array
- this.currentProcess.nodes = [...this.currentProcess.nodes, nodeCopy];
-
- // Update selection
- this.selectedNodeId = nodeCopy.id;
-
- // Save to history
- this.saveToHistory('Add node');
- this.unsavedChanges = true;
+ // Create a deep copy to avoid reference issues
+ const nodeCopy = JSON.parse(JSON.stringify(newNode));
- return nodeCopy;
+ // Add to current process nodes array using push to avoid creating new array reference
+ // This prevents triggering the watch that monitors currentProcess changes
+ this.currentProcess.nodes.push(nodeCopy);
+
+ // Update selection
+ this.selectedNodeId = nodeCopy.id;
+
+ // Save to history
+ this.saveToHistory('Add node');
+ this.unsavedChanges = true;
+
+ console.log('📝 Store: Added node', nodeCopy.id, 'Total nodes:', this.currentProcess.nodes.length);
+
+ return nodeCopy;
+ } finally {
+ // Reset flag after operation completes
+ this.isModifyingNodes = false;
+ }
},
/**
@@ -706,36 +718,46 @@ export const useProcessBuilderStore = defineStore('processBuilder', {
deleteNode(nodeId) {
if (!this.currentProcess) return;
- // Find the node index
- const index = this.currentProcess.nodes.findIndex(n => n.id === nodeId);
- if (index !== -1) {
- // Remove the node
- this.currentProcess.nodes.splice(index, 1);
-
- // Remove any edges connected to this node
- const edgesToRemove = this.currentProcess.edges.filter(
- edge => edge.source === nodeId || edge.target === nodeId
- );
-
- edgesToRemove.forEach(edge => {
- const edgeIndex = this.currentProcess.edges.findIndex(e => e.id === edge.id);
- if (edgeIndex !== -1) {
- this.currentProcess.edges.splice(edgeIndex, 1);
+ // Set flag to prevent interference during node deletion
+ this.isModifyingNodes = true;
+
+ try {
+ // Find the node index
+ const index = this.currentProcess.nodes.findIndex(n => n.id === nodeId);
+ if (index !== -1) {
+ // Remove the node using splice to avoid creating new array reference
+ this.currentProcess.nodes.splice(index, 1);
+
+ // Remove any edges connected to this node
+ const edgesToRemove = this.currentProcess.edges.filter(
+ edge => edge.source === nodeId || edge.target === nodeId
+ );
+
+ edgesToRemove.forEach(edge => {
+ const edgeIndex = this.currentProcess.edges.findIndex(e => e.id === edge.id);
+ if (edgeIndex !== -1) {
+ this.currentProcess.edges.splice(edgeIndex, 1);
+ }
+ });
+
+ // Clear selection if the deleted node was selected
+ if (this.selectedNodeId === nodeId) {
+ this.selectedNodeId = null;
}
- });
- // Clear selection if the deleted node was selected
- if (this.selectedNodeId === nodeId) {
- this.selectedNodeId = null;
+ this.saveToHistory('Delete node');
+ this.unsavedChanges = true;
+
+ console.log('🗑️ Store: Deleted node', nodeId, 'Total nodes:', this.currentProcess.nodes.length);
+
+ return true; // Return success
}
-
- this.saveToHistory('Delete node');
- this.unsavedChanges = true;
- return true; // Return success
+ return false; // Return failure
+ } finally {
+ // Reset flag after operation completes
+ this.isModifyingNodes = false;
}
-
- return false; // Return failure
},
/**
@@ -744,22 +766,33 @@ export const useProcessBuilderStore = defineStore('processBuilder', {
addEdge(edge) {
if (!this.currentProcess) return;
- const newEdge = {
- id: edge.id || `${edge.source}-${edge.target}`,
- source: edge.source,
- target: edge.target,
- label: edge.label || '',
- type: edge.type || 'default',
- animated: edge.animated !== undefined ? edge.animated : true,
- data: edge.data || {}
- };
+ // Set flag to prevent interference during edge addition
+ this.isModifyingNodes = true;
- this.currentProcess.edges.push(newEdge);
- this.selectedEdgeId = newEdge.id;
- this.saveToHistory('Add edge');
- this.unsavedChanges = true;
+ try {
+ const newEdge = {
+ id: edge.id || `${edge.source}-${edge.target}`,
+ source: edge.source,
+ target: edge.target,
+ label: edge.label || '',
+ type: edge.type || 'default',
+ animated: edge.animated !== undefined ? edge.animated : true,
+ data: edge.data || {}
+ };
- return newEdge;
+ // Use push to avoid creating new array reference
+ this.currentProcess.edges.push(newEdge);
+ this.selectedEdgeId = newEdge.id;
+ this.saveToHistory('Add edge');
+ this.unsavedChanges = true;
+
+ console.log('🔗 Store: Added edge', newEdge.id, 'Total edges:', this.currentProcess.edges.length);
+
+ return newEdge;
+ } finally {
+ // Reset flag after operation completes
+ this.isModifyingNodes = false;
+ }
},
/**